grigorevp
grigorevp

Reputation: 701

Invalid Mutability Exception when using background threads in KMM project on iOS

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

Answers (1)

Kevin Galligan
Kevin Galligan

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

Related Questions