Reputation: 701
I am working on a KMM project and am currently developing a model layer. In order to work with data I was planning to create a singleton like so:
@ThreadLocal object Repository {
private var dao: DataAccessObject? = null
private val scope = CoroutineScope(Dispatchers.Main)
fun injectDao(dao: DataAccessObject) {
scope.async {
Repository.dao = dao
}
}
suspend fun create(dataObjectType: TypeOfDataObject): DataObject? {
var dataObject: DataObject? = null
val job = scope.async {
dataObject = dao?.create(dataObjectType = dataObjectType)
}
job.await()
return dataObject
}
}
In such implementation as you see the request to the database is handled in Main
thread, which's quite not good. But it works and the data is returned from the function correctly. The next obvious step is to try to run it in background scope. To make it we should just redeclare scope
:
private val scope = CoroutineScope(Dispatchers.Default)
When we run the code and call create
function from somewhere it falls with
Uncaught Kotlin exception: kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen kotlin.native.internal.Ref@5761ad88
2021-02-02 23:54:50.408645+0300 Plendy[28960:2893398] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
at 0 Shared 0x000000010d51640f kfun:kotlin.Throwable#<init>(kotlin.String?){} + 95 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/Throwable.kt:23:37)
at 1 Shared 0x000000010d50f0bd kfun:kotlin.Exception#<init>(kotlin.String?){} + 93 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/Exceptions.kt:23:44)
at 2 Shared 0x000000010d50f32d kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 93 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/Exceptions.kt:34:44)
at 3 Shared 0x000000010d5448cd kfun:kotlin.native.concurrent.InvalidMutabilityException#<init>(kotlin.String){} + 93 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/native/concurrent/Freezing.kt:22:60)
at 4 Shared 0x000000010d5460af ThrowInvalidMutabilityException + 431 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/native/concurrent/Internal.kt:92:11)
at 5 Shared 0x000000010d6470b0 MutationCheck + 128
at 6 Shared 0x000000010d5640f8 kfun:kotlin.native.internal.Ref#<set-element>(1:0){} + 104 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/native/internal/Ref.kt:12:5)
at 7 Shared 0x000000010d4a1d5b kfun:com.plendy.PlendyCore.Model.KNPlendyData.$create$lambda-1COROUTINE$4.invokeSuspend#internal + 779 (/Users/petr/Documents/Projects/Plendy/Android/Plendy/PlendyCore/src/commonMain/kotlin/com/plendy/PlendyCore/Model/KNPlendyData.kt:23:13)
at 8 Shared 0x000000010d537958 kfun:kotlin.coroutines.native.internal.BaseContinuationImpl#resumeWith(kotlin.Result<kotlin.Any?>){} + 760 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:30:39)
at 9 Shared 0x000000010d6d7a78 kfun:kotlinx.coroutines.DispatchedTask#run(){} + 2680 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt:106:71)
at 10 Shared 0x000000010d687fb8 kfun:kotlinx.coroutines.EventLoopImplBase#processNextEvent(){}kotlin.Long + 840 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/EventLoop.common.kt:274:18)
at 11 Shared 0x000000010d6efbbb kfun:kotlinx.coroutines#runEventLoop(kotlinx.coroutines.EventLoop?;kotlin.Function0<kotlin.Boolean>){} + 843 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Builders.kt:80:40)
at 12 Shared 0x000000010d6f8d39 kfun:kotlinx.coroutines.WorkerCoroutineDispatcherImpl.start$lambda-0#internal + 409 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Workers.kt:49:17)
at 13 Shared 0x000000010d6f8f30 kfun:kotlinx.coroutines.WorkerCoroutineDispatcherImpl.$start$lambda-0$FUNCTION_REFERENCE$35.invoke#internal + 64 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Workers.kt:47:24)
at 14 Shared 0x000000010d6f8f90 kfun:kotlinx.coroutines.WorkerCoroutineDispatcherImpl.$start$lambda-0$FUNCTION_REFERENCE$35.$<bridge-UNN>invoke(){}#internal + 64 (/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Workers.kt:47:24)
at 15 Shared 0x000000010d545d59 WorkerLaunchpad + 185 (/Users/teamcity/buildAgent/work/f01984a9f5203417/runtime/src/main/kotlin/kotlin/native/concurrent/Internal.kt:69:54)
at 16 Shared 0x000000010d64ba4f _ZN6Worker19processQueueElementEb + 3135
at 17 Shared 0x000000010d64adf6 _ZN12_GLOBAL__N_113workerRoutineEPv + 54
at 18 libsystem_pthread.dylib 0x0000000110a86950 _pthread_start + 224
at 19 libsystem_pthread.dylib 0x0000000110a8247b thread_start + 15
What is strange that the data is written to the db, meaning that dao
is called successfully, but data isn't returning from the function, because the exception occurs earlier. At this point I don't understand to what frozen object does the exception relates? What I've tried next is to remove job.await()
line and it worked perfectly with no exceptions besides of cause having null in function's output.
So my question is: how to make code run in a background thread still having an ability to wait for its output?
Upvotes: 0
Views: 1091
Reputation: 17352
You should include more of the exception info to help figure out what's happening, and you can use ensureNeverFrozen
to help identify when something is being inadvertently frozen. However, in this case, I think I can figure it out.
In this case, capturing a reference to dataObject
in your background lambda will freeze it. Trying to reassign it is (probably) throwing your exception.
var dataObject: DataObject? = null
val job = scope.async {
//Trying to assign the frozen dataObject will fail
dataObject = dao?.create(dataObjectType = dataObjectType)
}
Since you're already in a suspend function, why not just use something like withContext
?
suspend fun create(dataObjectType: TypeOfDataObject): DataObject? {
val dataObject = withContext(Dispatchers.Default) {
dao?.create(dataObjectType = dataObjectType)
}
return dataObject
}
And if you're going that far ...
suspend fun create(dataObjectType: TypeOfDataObject): DataObject? = withContext(Dispatchers.Default) {
dao?.create(dataObjectType = dataObjectType)
}
Upvotes: 3