Search code examples
kotlin-coroutineskotlin-multiplatform

InvalidMutabilityExceptionInvalidMutabilityException on common test with Ktor


I have a multiplatform library to do few APIs calls with Ktor (2.0.0-beta-1)

class DiscoveryServicesImpl(private val client: HttpClient) : DiscoveryServices {
    override suspend fun getServers(domain: String): DAAServers {
        return client.get(Servers.Domain(domain = domain)).body()
    }
}

// Where client is created with specific engine (OkHttp for Android and Darwin for iOS)

This code works as expected (implemented in both Android and iOS apps)

To secure this code, I've added a unit test to verify that the url is well builded.

@Test
fun getServersSuccessful() {
    runBlocking {
        var _request: HttpRequestData? = null

        //Given
        val mockEngine = MockEngine {
            _request = it
        }
        val discoveryApiMock = DiscoveryApi(mockEngine)
        val service = DiscoveryServicesImpl(discoveryApiMock.client)

        //When
        val servers: DAAServers = service.getServers(domain)

        //Then
        assertEquals("https://****/$domain", _request!!.url.toString())
    }

}

But when a run this test I got an error (for iOS test only) :

> Task :api:discovery:iosTest

***.discovery.DiscoveryServicesImplTest.getServersSuccessful FAILED
    kotlin.native.concurrent.InvalidMutabilityException at /Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Throwable.kt:24



kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen kotlin.native.internal.Ref@15cb7c8
kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen kotlin.native.internal.Ref@15cb7c8
    at kotlin.Throwable#<init>(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Throwable.kt:24)
    at kotlin.Exception#<init>(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:23)
    at kotlin.RuntimeException#<init>(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:34)
    at kotlin.native.concurrent.InvalidMutabilityException#<init>(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Freezing.kt:24)
    at <global>.ThrowInvalidMutabilityException(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Internal.kt:109)
    at <global>.MutationCheck(Unknown Source)
    at kotlin.native.internal.Ref#<set-element>(/Users/teamcity1/teamcity_work/6326934d18cfe24e/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/Ref.kt:12)

It's happening only on the ios target

To fix this, I've added this code pretty much everywhere (commonMain, commonTest, iosMain, iosTest) but it doesn't change anything

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-native-mt") {
    version {
        strictly("1.6.0-native-mt")
    }
}

Below, my gradles files :

kotlin {
    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation(Ktor.clientCore)
                implementation(Ktor.clientResources)
                implementation(Ktor.contentNegotiation)
                implementation(Ktor.clientJson)
                implementation(Ktor.clientLogging)
                implementation(Ktor.clientAuth)
                implementation(Koin.koinMultiplatform)
                implementation(project(":serialization"))
                implementation(project(":transverse:log"))

                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-native-mt") {
                    version {
                        strictly("1.6.0-native-mt")
                    }
                }

            }
        }

        val commonTest by getting {
            dependencies {
                implementation(Ktor.clientMock)
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0-native-mt") {
                    version {
                        strictly("1.6.0-native-mt")
                    }
                }
            }
        }


        val iosMain by getting {
            dependencies {
                implementation(Ktor.clientDarwin)
            }
        }
    }
}


Solution

  • If you're using the latest version of ktor and coroutines, you'll probably want to use the new memory model, and just the 1.6.0 kotlinx.coroutines rather than the native-mt version.

    https://blog.jetbrains.com/kotlin/2021/08/try-the-new-kotlin-native-memory-manager-development-preview/

    https://github.com/touchlab/KaMPKit/blob/main/gradle.properties#L26

    Most library dev from Jetbrains will likely focus on the new memory model from now on, so unless there are specific near term concerns on moving, I would do that.