Platform: iOS, org.jetbrains.kotlinx:kotlinx-coroutines-core-iosarm64
Version: 1.3.5-native-mt (so multithreading should be supported in iOS?)
I simply want to operate in the UI thread by default, and do some actions, e.g. network calls, in the background thread, and then return some data back to the iOS app in the UI thread. Trivially.
For now, I have:
actual object DispatchersProvider {
actual val dispatcherDefault: CoroutineDispatcher = IosBackgroundDispatcher
actual val dispatcherUi: CoroutineDispatcher = IosMainDispatcher
}
private object IosMainDispatcher : CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
// should I freeze `dispatch_get_main_queue`? or `block` before `run`?
dispatch_async(dispatch_get_main_queue(), block::run)
}
}
private object IosBackgroundDispatcher : CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND.convert(), 0.convert()), block::run)
}
}
It seems like ok with the main dispatcher, but when I'm trying to do something like this:
class IssueReproduce: CoroutineScope {
private val dispatcher = DispatchersProvider.dispatcherUi
private val job = SuperviserJob()
private val someApi = SomeApi()
override val coroutineContext: CoroutineContext
get() = dispatcher + job
fun performApiCall(callback: (SomeDataClass) -> Unit) {
launch(coroutineContext) {
val response = withContext(DispatchersProvider.dispatcherDefault) {
someApi.someMethod()
}
callback(response)
}
}
}
/* ... */
class SomeApi {
private val client: HttpClient = /* ... */
suspend fun someMethod(): SomeDataClass {
return client.get { /* ... */ }
}
}
then I get errors like this:
Uncaught Kotlin exception: kotlin.native.IncorrectDereferenceException: illegal attempt to access non-shared com.example.mobile.IosBackgroundDispatcher.$run$FUNCTION_REFERENCE$20@81b41e88 from other thread
at 0 ExampleAppCommon 0x0000000103846310 kfun:kotlin.Throwable.<init>(kotlin.String?)kotlin.Throwable + 92
at 1 ExampleAppCommon 0x000000010383f170 kfun:kotlin.Exception.<init>(kotlin.String?)kotlin.Exception + 88
at 2 ExampleAppCommon 0x000000010383ec34 kfun:kotlin.RuntimeException.<init>(kotlin.String?)kotlin.RuntimeException + 88
at 3 ExampleAppCommon 0x000000010386da5c kfun:kotlin.native.IncorrectDereferenceException.<init>(kotlin.String)kotlin.native.IncorrectDereferenceException + 88
at 4 ExampleAppCommon 0x0000000103871284 ThrowIllegalObjectSharingException + 496
at 5 ExampleAppCommon 0x0000000103c60478 _ZNK16KRefSharedHolder3refEv + 100
at 6 ExampleAppCommon 0x0000000103c3adb8 _ZL39Kotlin_Interop_unwrapKotlinObjectHolderP11objc_object + 48
at 7 ExampleAppCommon 0x0000000103ba145c _4e6967687453746f7279436f6d6d6f6e_knbridge4 + 120
at 8 libdispatch.dylib 0x0000000104429d10 _dispatch_call_block_and_release + 32
at 9 libdispatch.dylib 0x000000010442b18c _dispatch_client_callout + 20
at 10 libdispatch.dylib 0x000000010443d8b8 _dispatch_root_queue_drain + 908
at 11 libdispatch.dylib 0x000000010443e030 _dispatch_worker_thread2 + 140
at 12 libsystem_pthread.dylib 0x00000001b45c36d8 _pthread_wqthread + 216
at 13 libsystem_pthread.dylib 0x00000001b45c99c8 start_wqthread + 8
(lldb)
If I change dispatcherDefault to the Dispatchers.Default, then I get errors like this:
mutation attempt of frozen io.ktor.client.request.HttpRequestPipeline@83d78848, e=kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen io.ktor.client.request.HttpRequestPipeline@83d78848
You don't need to implement anything with -native-mt version:
Dispatchers.MainDispatchers.DefaultIn particular, you cannot use dispatch_get_global_queue(QOS_CLASS_BACKGROUND) to dispatch coroutines in Kotlin/Native, because this background queue is not bound to any particular thread and Kotlin/Native does not allow moving mutable objects (including coroutines) between threads.
Does it help?
@elizarov
Okay, I changed dispatchers to this:
actual object DispatchersProvider {
actual val dispatcherDefault: CoroutineDispatcher = Dispatchers.Default // IosBackgroundDispatcher
actual val dispatcherUi: CoroutineDispatcher = Dispatchers.Main // IosMainDispatcher
}
Then I get this error:
Caused by: kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen io.ktor.client.request.HttpRequestPipeline@81ed68c8
at 0 ExampleAppCommon 0x00000001056376e8 kfun:kotlin.Throwable.<init>(kotlin.String?)kotlin.Throwable + 92
at 1 ExampleAppCommon 0x0000000105630548 kfun:kotlin.Exception.<init>(kotlin.String?)kotlin.Exception + 88
at 2 ExampleAppCommon 0x000000010563000c kfun:kotlin.RuntimeException.<init>(kotlin.String?)kotlin.RuntimeException + 88
at 3 ExampleAppCommon 0x0000000105660fe8 kfun:kotlin.native.concurrent.InvalidMutabilityException.<init>(kotlin.String)kotlin.native.concurrent.InvalidMutabilityException + 88
at 4 ExampleAppCommon 0x000000010566242c ThrowInvalidMutabilityException + 468
at 5 ExampleAppCommon 0x0000000105a4ef68 MutationCheck + 132
at 6 ExampleAppCommon 0x000000010587b94c kfun:io.ktor.util.pipeline.Pipeline.<set-interceptors>#internal + 104
at 7 ExampleAppCommon 0x000000010587be20 kfun:io.ktor.util.pipeline.Pipeline.notSharedInterceptorsList#internal + 88
at 8 ExampleAppCommon 0x000000010587a450 kfun:io.ktor.util.pipeline.Pipeline.cacheInterceptors#internal + 504
at 9 ExampleAppCommon 0x000000010587bcdc kfun:io.ktor.util.pipeline.Pipeline.sharedInterceptorsList#internal + 292
at 10 ExampleAppCommon 0x00000001058771d4 kfun:io.ktor.util.pipeline.Pipeline.createContext#internal + 200
at 11 ExampleAppCommon 0x000000010587703c kfun:io.ktor.util.pipeline.Pipeline.execute(TContext;TSubject)TSubject + 212
at 12 ExampleAppCommon 0x00000001058b6008 kfun:io.ktor.client.HttpClient.$executeCOROUTINE$16.invokeSuspend(kotlin.Result<kotlin.Any?>)kotlin.Any? + 504
at 13 ExampleAppCommon 0x00000001058b62d4 kfun:io.ktor.client.HttpClient.execute(io.ktor.client.request.HttpRequestBuilder)io.ktor.client.call.HttpClientCall + 256
at 14 ExampleAppCommon 0x00000001058ebb38 kfun:io.ktor.client.statement.HttpStatement.$executeUnsafeCOROUTINE$50.invokeSuspend(kotlin.Result<kotlin.Any?>)kotlin.Any? + 712
at 15 ExampleAppCommon 0x00000001058ebdec kfun:io.ktor.client.statement.HttpStatement.executeUnsafe$ktor-client-core()io.ktor.client.statement.HttpResponse + 212
at 16 ExampleAppCommon 0x0000000105974e74 kfun:com.example.mobile.data.api.SomeApi.$someMethodCOROUTINE$3.invokeSuspend(kotlin.Result<kotlin.Any?>)kotlin.Any? + 4560
at 17 ExampleAppCommon 0x0000000105975c14 kfun:com.example.mobile.data.api.SomeApi.someMethod(kotlin.Int;kotlin.Int;kotlin.String)com.example.mobile.data.model.dto.ContentResponse<com.example.mobile.data.model.dto.SomeDataClass> + 308
at 18 ExampleAppCommon 0x000000010598fdd8 kfun:com.example.mobile.presentation.IssueReproduce.$performApiCall$lambda-1$lambda-0COROUTINE$6.invokeSuspend#internal + 520
at 19 ExampleAppCommon 0x0000000105655b8c kfun:kotlin.coroutines.native.internal.BaseContinuationImpl.resumeWith(kotlin.Result<kotlin.Any?>) + 524
at 20 ExampleAppCommon 0x000000010579c304 kfun:kotlinx.coroutines.DispatchedTask.run() + 2284
at 21 ExampleAppCommon 0x000000010575ccec kfun:kotlinx.coroutines.EventLoopImplBase.processNextEvent()kotlin.Long + 620
at 22 ExampleAppCommon 0x00000001057b1f7c kfun:kotlinx.coroutines.runEventLoop$kotlinx-coroutines-core(kotlinx.coroutines.EventLoop?;kotlin.Function0<kotlin.Boolean>) + 744
at 23 ExampleAppCommon 0x00000001057b88fc kfun:kotlinx.coroutines.WorkerCoroutineDispatcherImpl.start$lambda-0#internal + 312
at 24 ExampleAppCommon 0x00000001057b8ac4 kfun:kotlinx.coroutines.WorkerCoroutineDispatcherImpl.$start$lambda-0$FUNCTION_REFERENCE$135.invoke#internal + 64
at 25 ExampleAppCommon 0x00000001057b8b24 kfun:kotlinx.coroutines.WorkerCoroutineDispatcherImpl.$start$lambda-0$FUNCTION_REFERENCE$135.$<bridge-UNN>invoke()#internal + 64
at 26 ExampleAppCommon 0x00000001056620e0 WorkerLaunchpad + 188
at 27 ExampleAppCommon 0x0000000105a64034 _ZN6Worker19processQueueElementEb + 1924
at 28 ExampleAppCommon 0x0000000105a652ec _ZN12_GLOBAL__N_113workerRoutineEPv + 72
at 29 libsystem_pthread.dylib 0x00000001b45c18fc _pthread_start + 168
(lldb)
Is this related to ktor, maybe? The logic is simple and shown above: I have HttpClient() of ktor-client-ios.
Yes, that's a ktor issue. You cannot share HttpClient between threads in Kotlin/Native. The recommended way is to create HttpClient in your Main dispatcher and it will take care of performing all the actual HTTP work in the background itself. You don't have to worry about it.
Most helpful comment
Yes, that's a
ktorissue. You cannot shareHttpClientbetween threads in Kotlin/Native. The recommended way is to createHttpClientin yourMaindispatcher and it will take care of performing all the actual HTTP work in the background itself. You don't have to worry about it.