Kodein + Ktor = 冰冻变异尝试 kotlin.collections.HashMap - 为什么?

Kodein + Ktor = mutation attempt of frozen kotlin.collections.HashMap - why?

最近几天我一直在为这个异常而苦恼。

我有一个具有这些依赖项的 kotlin 多平台项目:

而且我在尝试在本机中使用 httpClient 时遇到了异常:

    at kotlin.Throwable#<init>(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/Throwable.kt:23)
    at kotlin.Exception#<init>(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/Exceptions.kt:23)
    at kotlin.RuntimeException#<init>(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/Exceptions.kt:34)
    at kotlin.native.concurrent.InvalidMutabilityException#<init>(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/native/concurrent/Freezing.kt:22)
    at <global>.ThrowInvalidMutabilityException(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/native/concurrent/Internal.kt:93)
    at <global>.MutationCheck(Unknown Source)
    at kotlin.collections.HashMap.<set-length>#internal(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/collections/HashMap.kt:16)
    at kotlin.collections.HashMap#addKey(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/collections/HashMap.kt:292)
    at kotlin.collections.HashMap#put(/Users/teamcity3/buildAgent/work/290aee0e088a1666/runtime/src/main/kotlin/kotlin/collections/HashMap.kt:68)
    at org.kodein.di.internal.DITreeImpl#find(/Users/runner/work/Kodein-DI/Kodein-DI/kodein-di/src/commonMain/kotlin/org/kodein/di/internal/DITreeImpl.kt:132)
    at org.kodein.di.DITree#find$default(/Users/runner/work/Kodein-DI/Kodein-DI/kodein-di/src/commonMain/kotlin/org/kodein/di/DITree.kt:36)
    at org.kodein.di.internal.DIContainerImpl#factory(/Users/runner/work/Kodein-DI/Kodein-DI/kodein-di/src/commonMain/kotlin/org/kodein/di/internal/DIContainerImpl.kt:158)
    at org.kodein.di.DIContainer#factory$default(/Users/runner/work/Kodein-DI/Kodein-DI/kodein-di/src/commonMain/kotlin/org/kodein/di/DIContainer.kt:32)
    at org.kodein.di.DIContainer#provider(/Users/runner/work/Kodein-DI/Kodein-DI/kodein-di/src/commonMain/kotlin/org/kodein/di/DIContainer.kt:76)
    at org.kodein.di.DIContainer#provider$default(/Users/runner/work/Kodein-DI/Kodein-DI/kodein-di/src/commonMain/kotlin/org/kodein/di/DIContainer.kt:75)
    at org.kodein.di.internal.DirectDIBaseImpl#Instance(/Users/runner/work/Kodein-DI/Kodein-DI/kodein-di/src/commonMain/kotlin/org/kodein/di/internal/DirectDIImpl.kt:30)
    at InvalidMutabilitySampleTest.<init>$lambda-6$lambda-5$lambda-4$lambda-3$lambda-2#internal(/Users/r.juszczyk/StudioProjects/lmhu-multiplatform-app/MultiplatformApp/src/iosTest/kotlin/InvalidMutabilitySampleTest.kt:26)
    at InvalidMutabilitySampleTest.$<init>$lambda-6$lambda-5$lambda-4$lambda-3$lambda-2$FUNCTION_REFERENCE.invoke#internal(/Users/r.juszczyk/StudioProjects/lmhu-multiplatform-app/MultiplatformApp/src/iosTest/kotlin/InvalidMutabilitySampleTest.kt:25)
    at InvalidMutabilitySampleTest.$<init>$lambda-6$lambda-5$lambda-4$lambda-3$lambda-2$FUNCTION_REFERENCE.$<bridge-UNNN>invoke(/Users/r.juszczyk/StudioProjects/lmhu-multiplatform-app/MultiplatformApp/src/iosTest/kotlin/InvalidMutabilitySampleTest.kt:25)
    at io.ktor.client.HttpClientConfig.install$<anonymous>_1#internal(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClientConfig.kt:69)
    at io.ktor.client.HttpClientConfig.$install$<anonymous>_1$FUNCTION_REFERENCE.invoke#internal(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClientConfig.kt:65)
    at io.ktor.client.HttpClientConfig.$install$<anonymous>_1$FUNCTION_REFERENCE.$<bridge-UNNN>invoke(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClientConfig.kt:65)
    at io.ktor.client.features.json.JsonFeature.Feature#prepare(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-features/ktor-client-json/common/src/io/ktor/client/features/json/JsonFeature.kt:129)
    at io.ktor.client.HttpClientConfig.install$<anonymous>_1-2#internal(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClientConfig.kt:77)
    at io.ktor.client.HttpClientConfig.$install$<anonymous>_1-2$FUNCTION_REFERENCE.invoke#internal(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClientConfig.kt:74)
    at io.ktor.client.HttpClientConfig.$install$<anonymous>_1-2$FUNCTION_REFERENCE.$<bridge-UNNN>invoke(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClientConfig.kt:74)
    at io.ktor.client.HttpClientConfig#install(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClientConfig.kt:97)
    at io.ktor.client.HttpClient#<init>(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClient.kt:172)
    at io.ktor.client.HttpClient#<init>(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClient.kt:81)
    at io.ktor.client#HttpClient(/Users/administrator/Documents/agent/work/8d547b974a7be21f/ktor-client/ktor-client-core/common/src/io/ktor/client/HttpClient.kt:43)

我设法在测试中重现了那个崩溃:

class InvalidMutabilitySampleTest {
    val di = DI {
        import(DI.Module("Some Module") {
            bind<Json>() with provider {
                Json {
                    prettyPrint = true
                    isLenient = true
                }
            }

            bind<HttpClient>() with provider {
                HttpClient{
                    install(JsonFeature) {
                        serializer = KotlinxSerializer(instance())
                    }
                }
            }
        })
    }

    val httpClient: HttpClient by di.instance()

    @Test
    fun invalidMutabilityTest() {
        println(httpClient)
    }

}

我也设法通过更改来修复它:

HttpClient{
   install(JsonFeature) {
      serializer = KotlinxSerializer(instance())
   }
}

至:

val json = instance<Json>()
HttpClient{
   install(JsonFeature) {
      serializer = KotlinxSerializer(json)
   }
}

然后我注意到 HttpClient 做了一件非常具体的事情 - 它在 init 块中冻结了自己。 我设法用这个示例代码重现了它:

class FrozenConstructor(val block: ()->Unit) {
    init {
        freeze()
    }
}

class InvalidMutabilitySampleTest2 {
    val di = DI {
        import(DI.Module("Some Module") {
            bind<String>() with provider {
                "lolo"
            }

            bind<FrozenConstructor>() with provider {
                FrozenConstructor{
                    instance<String>()
                }
            }
        })
    }

    val frozenConstructor: FrozenConstructor by di.instance()

    @Test
    fun invalidMutabilityTest() {
        println(frozenConstructor)
    }
}

所以我的理论如下:

  1. kodein 尝试提供 FrozenConstructor class
  2. FrozenConstructor 被创建并冻结自身,以及它引用 kodein
  3. 的成员
  4. kodein 尝试缓存提供的依赖项并尝试改变内部 MutableMap,它被冻结并且一切都崩溃了

有人可以确认这或多或少是正确的,如果不正确请纠正我?

还有你们能建议最好的处理方法,以及其他等待那里的陷阱吗?

是否是 kodein 错误?

如果我使用 with provider 而不是 with singleton 为什么 kodein 必须在可变映射中存储一些东西?

我想你已经很明白了。 Ktor 冻结了自身及其所有配置,以确保它可以在 Kotlin/Native 中跨线程使用。 Kodein 假设你只会从一个线程中接触它并且冻结是不安全的。 (无论这是限制、错误还是设计缺陷,都可能需要解释。)

要解决这些问题,您需要避免在 HttpClient 配置中意外捕获引用 Kodein 内部结构的 this 引用。正如您所发现的,一个很好的方法是在 HttpClient lambda 之外的辅助变量中从 DI 中获取实例。

你是对的。 FrozenConstructor 冻结自身,因此其 block 属性,作为 lambda,冻结其所有捕获,包括 DI 容器。

Kodein-DI 不支持冻结(因此不支持原生多线程)。 自 JB 宣布开发新的 GC 以来,已决定不投资使其与本机多线程兼容。