ktor: kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen kotlinx.coroutines.StandaloneCoroutine@28a82d8

works on android, breaks on ios

code:

fun triggerNetworkCall(){
      GlobalScope.apply {
            launch(Background) {
                try {
                    val data = repository.getSettings()
                } catch (t: Throwable) {
                    Logger.d("ktor", t.message?:"null")
                }
            }
        }
}
 suspend fun getSettings(): String? {
        val response =  apiConfig.getReferralData()
        return response
}
suspend fun getReferralData(): String? {
       val localClient = HttpClient(PlatformHttpClient.httpClientEngine){
            install(JsonFeature)
        }
        return localClient.post {
            url {
                protocol = URLProtocol.HTTPS
                host = "postman-echo.com"
                encodedPath = "post"
            }
            contentType(ContentType.Application.Json)
            body = "{}"
            HttpMethod.Post
        }
}

This post request works in android but fails in iOS with following stack trace:

Uncaught Kotlin exception: kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen kotlinx.coroutines.StandaloneCoroutine@1a8b4e8

whereas a get request works on both platforms. Following is the function being used:

suspend fun getReferralData(): String? {
       val localClient = HttpClient(PlatformHttpClient.httpClientEngine){
            install(JsonFeature)
        }

        val address = Url("https://postman-echo.com/get?foo1=bar1&foo2=bar2")
        return localClient.get {
            url {
                url(address.toString())
            }
        }
}

Dispatchers definition: iOS

internal actual val Main: CoroutineDispatcher = NsQueueDispatcher(dispatch_get_main_queue())

internal actual val Background: CoroutineDispatcher = Main


internal class NsQueueDispatcher(
    private val dispatchQueue: dispatch_queue_t
) : CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        dispatch_async(dispatchQueue) {
            block.run()
        }
    }
}

android

internal actual val Main: CoroutineDispatcher = Dispatchers.Main

internal actual val Background: CoroutineDispatcher = Dispatchers.Default

dependencies

commonMain {

            dependencies {
                implementation "org.jetbrains.kotlin:kotlin-stdlib-common:1.3.61"
                implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:0.14.0"
                implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.2-1.3.60"
                implementation "co.touchlab:stately:0.9.4"
                implementation "co.touchlab:stately-collections:0.9.4"
                implementation "io.ktor:ktor-client-core:1.2.6"
                implementation "io.ktor:ktor-client-json:1.2.6"
                implementation "io.ktor:ktor-client-logging:1.2.6"
                implementation "io.ktor:ktor-client-serialization:1.2.6"
                implementation "com.github.aakira:napier:1.1.0"

            }
        }

        androidMain {
            dependencies {
                implementation "org.jetbrains.kotlin:kotlin-stdlib:1.3.61"
                implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0"
                implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2-1.3.60"
                implementation "io.ktor:ktor-client-core-jvm:1.2.6"
                implementation "io.ktor:ktor-client-json-jvm:1.2.6"
                implementation "io.ktor:ktor-client-logging-jvm:1.2.6"
                implementation "io.ktor:ktor-client-serialization-jvm:1.2.6"
                implementation "io.ktor:ktor-client-android:1.2.6"
                implementation "com.github.aakira:napier-android:1.1.0"
            }
        }

        iosMain {
            dependencies {
                implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:0.14.0"
                implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.3.2-1.3.60"

                implementation "io.ktor:ktor-client-core-native:1.2.6"
                implementation "io.ktor:ktor-client-json-native:1.2.6"
                implementation "io.ktor:ktor-client-logging-native:1.2.6"
                implementation "io.ktor:ktor-client-serialization-native:1.2.6"
                implementation "io.ktor:ktor-client-ios:1.2.6"
                implementation "com.github.aakira:napier-ios:1.1.0"
            }
        }

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 23 (2 by maintainers)

Most upvoted comments

This issue happens on version 1.4.0 while on 1.3.2-1.4.0-rc everything works fine.

Having a very similar issue on 1.4.0 when doing a simple get request on ios: Uncaught Kotlin exception: kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen kotlinx.coroutines.ChildHandleNode@31dca08 reverting back to 1.3.2-1.4.0-rc works like a charm.

same error with me.

@joaquim-verges I solve it.

kotlin_version=1.4.0
kotlinx_coroutines_version=1.3.9-native-mt
kotlinx_ktor_version=1.4.0
kotlinx_serialization_version=1.0.0-RC
// ...
sourceSets {
        val commonMain by sourceSets.getting {
            dependencies {
                api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version")
                api("org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlinx_serialization_version")
                api("io.ktor:ktor-client-core:$kotlinx_ktor_version")
                api("io.ktor:ktor-client-json:$kotlinx_ktor_version")
                api("io.ktor:ktor-client-serialization:$kotlinx_ktor_version")
            }
        }

        val iosMain by sourceSets.getting {
            dependencies {
                implementation("io.ktor:ktor-client-ios:$kotlinx_ktor_version")
            }
        }
        val iosX64Main by sourceSets.getting {
            dependencies {
            }
        }
        val iosArm64Main by sourceSets.getting {
            dependencies {
            }
        }
        val androidMain by sourceSets.getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinx_coroutines_version")
                implementation("io.ktor:ktor-client-android:$kotlinx_ktor_version")
            }
        }
    }

interface IUserApi {
    suspend fun users(): List<User>
}

expect val httpClientEngine: HttpClientEngine

class UserApi : IUserApi {

    private val baseUrl = "https://jsonplaceholder.typicode.com"
    private val client = HttpClient(httpClientEngine) {
        install(JsonFeature) {
            serializer = KotlinxSerializer(json = kotlinx.serialization.json.Json {
                isLenient = false
                ignoreUnknownKeys = true
                allowSpecialFloatingPointValues = true
                useArrayPolymorphism = false
            })
        }
    }

    override suspend fun users(): List<User> {
        return client.get {
            setupCall(Endpoint.USERS)
        }
    }

    private fun HttpRequestBuilder.setupCall(endpoint: Endpoint) {
        url {
            takeFrom(urlString = baseUrl)
            encodedPath += endpoint.path
        }
    }
}

import io.ktor.client.engine.*
import io.ktor.client.engine.ios.*

actual val httpClientEngine: HttpClientEngine
    get() = Ios.create()

Worked for me with version 1.3.9-native-mt-2

Fixed with 1.3.9-native-mt-2

Try creating a new HttpClient every time you use it instead of retaining it in an instance variable. Also disable logging (remove the logging config from the client config).

I got everything working on my side with kotlin 1.4.10, ktor 1.4.0, coroutines 1.3.9-mt-2 and kotlinx.serialization 1.0.0-RC. It even works in a background thread (Dispatchers.DEFAULT). You have to take care though to create a new http client in the background thread for every call and to ensure you don’t reference other classes there that are accessed from the main thread.

See the following sample code:

    private suspend fun <T> withHttpClient(perform: suspend HttpClient.() -> T): T {
        // Run the following in a background thread, the HttpClient itself has to be constructed in the background thread as well.
        return withContext(Dispatchers.Default) {
            // Specify the HttpClient configuration,
            // be sure to not capture any global references because they will be frozen otherwise.
            val configuration: HttpClientConfig<*>.() -> Unit = ... 
            val client = HttpClient(block = configuration)
            try {
                perform(client)
            } finally {
                client.close()
            }
        }
    }

    private fun <T: Any>deserialize(type: KClass<T>, bytes: ByteArray): T {
        // Implement any deserialization logic or just use kotlinx.serialization
    }

    /**
     * Sample HTTP get, result will be parsed in the background, but returned in the main thread
     */
    suspend fun <T : Any> get(
        path: String,
        responseType: KClass<T>,
        parameters: Map<String, Any>
    ): T {
        // This happens in the main thread
        return withHttpClient(accessToken) {
            // This happens all in a background thread
            get<ByteArray>(path = path) {
                for ((key, value) in parameters) {
                    parameter(key, value)
                }
            }.let {
                deserialize(responseType, it)
            }
        }
    }

I’m also facing the same issue on 1.4.0 and I thought my code was the issue or I misunderstood the variable freezing functionality. But as you all pointed out it is an issue with 1.4.0.

@e5l I understand that you have a huge load of work and that we all go to you when there is an issue especially with Ktor but we would very much appreciate any kind of update on this issue. If there is a workaround or should we revert back to 1.4.0-rc or if the issue is being worked on currently and expected to be fixed in the next version. Any kind of update would be very much appreciated.