```2021-06-11 15:15:27.627354+0200 TomE[409:67089]...
# touchlab-tools
l
Copy code
2021-06-11 15:15:27.627354+0200 TomE[409:67089] Debug: (DatabaseHelper) Getting all trackings from database
org.koin.core.error.InstanceCreationException: Could not create instance for [Single:'de.comp.proj.shared.ktor.TrackingKtorApi']
  at 0  shared               0x00000001034297f8 kfun:kotlin.Exception#<init>(kotlin.String?;kotlin.Throwable?){} + 120
  at 1  shared               0x000000010380ccc0 kfun:org.koin.core.error.InstanceCreationException#<init>(kotlin.String;kotlin.Exception){} + 120
  at 2  shared               0x000000010380e134 kfun:org.koin.core.instance.InstanceFactory#create(org.koin.core.instance.InstanceContext){}1:0 + 1472
  at 3  shared               0x000000010380e840 kfun:org.koin.core.instance.SingleInstanceFactory#create(org.koin.core.instance.InstanceContext){}1:0 + 268
  at 4  shared               0x000000010380ed0c kfun:org.koin.core.instance.SingleInstanceFactory.get$lambda-0#internal + 208
  at 5  shared               0x000000010380edb8 kfun:org.koin.core.instance.SingleInstanceFactory.$get$lambda-0$FUNCTION_REFERENCE$244.invoke#internal + 72
  at 6  shared               0x000000010380ef1c kfun:org.koin.core.instance.SingleInstanceFactory.$get$lambda-0$FUNCTION_REFERENCE$244.$<bridge-UNN>invoke(){}#internal + 68
  at 7  shared               0x0000000103829608 kfun:org.koin.mp.KoinPlatformTools#synchronized(kotlin.Any;kotlin.Function0<0:0>){0§<kotlin.Any?>}0:0 + 232
  at 8  shared               0x000000010380eae8 kfun:org.koin.core.instance.SingleInstanceFactory#get(org.koin.core.instance.InstanceContext){}1:0 + 364
  at 9  shared               0x0000000103815cf4 kfun:org.koin.core.registry.InstanceRegistry#resolveInstance(kotlin.String;kotlin.Function0<org.koin.core.parameter.DefinitionParameters>?){0§<kotlin.Any?>}0:0? + 508
  at 10 shared               0x000000010381f090 kfun:org.koin.core.scope.Scope.resolveInstance#internal + 840
  at 11 shared               0x000000010381ecb8 kfun:org.koin.core.scope.Scope#get(kotlin.reflect.KClass<*>;org.koin.core.qualifier.Qualifier?;kotlin.Function0<org.koin.core.parameter.DefinitionParameters>?){0§<kotlin.Any?>}0:0 + 1860
  at 12 shared               0x00000001038d3c38 kfun:de.comp.proj.shared.models.GeofencingModel.<init>$<anonymous>_1-1#internal + 800
  at 13 shared               0x00000001038d4c30 kfun:de.comp.proj.shared.models.GeofencingModel.$<init>$<anonymous>_1-1$FUNCTION_REFERENCE$169.invoke#internal + 152
  at 14 shared               0x000000010345da40 kfun:kotlin.native.concurrent.FreezeAwareLazyImpl.getOrInit#internal + 748
  at 15 shared               0x000000010345e0e4 kfun:kotlin.native.concurrent.FreezeAwareLazyImpl#<get-value>(){}1:0 + 500
  at 16 shared               0x00000001038ce674 kfun:de.comp.proj.shared.models.GeofencingModel.<get-trackingApi>#internal + 276
  at 17 shared               0x00000001038d04f4 kfun:de.comp.proj.shared.models.GeofencingModel.$sendGeofencingDataCOROUTINE$51#invokeSuspend(kotlin.Result<kotlin.Any?>){}kotlin.Any? + 460
  at 18 shared               0x00000001038d095c kfun:de.comp.proj.shared.models.GeofencingModel#sendGeofencingData(kotlin.collections.List<de.comp.proj.shared.entity.TrackingData>){} + 292
  at 19 shared               0x00000001038cfda4 kfun:de.comp.proj.shared.models.GeofencingModel.$saveGeofencingDataCOROUTINE$50#invokeSuspend(kotlin.Result<kotlin.Any?>){}kotlin.Any? + 1668
  at 20 shared               0x000000010345114c kfun:kotlin.coroutines.native.internal.BaseContinuationImpl#resumeWith(kotlin.Result<kotlin.Any?>){} + 560
  at 21 shared               0x00000001035c7fd0 kfun:kotlinx.coroutines.internal#resumeCancellableWith__at__kotlin.coroutines.Continuation<0:0>(kotlin.Result<0:0>;kotlin.Function1<kotlin.Throwable,kotlin.Unit>?){0§<kotlin.Any?>} + 2556
  at 22 shared               0x00000001035c8124 kfun:kotlinx.coroutines.internal#resumeCancellableWith$default__at__kotlin.coroutines.Continuation<0:0>(kotlin.Result<0:0>;kotlin.Function1<kotlin.Throwable,kotlin.Unit>?;kotlin.Int){0§<kotlin.Any?>} + 240
  at 23 shared               0x00000001035f7bc0 kfun:kotlinx.coroutines.internal.shareableInterceptedResumeCancellableWith$lambda-0#internal + 232
  at 24 shared               0x00000001035f7d78 kfun:kotlinx.coroutines.internal.$shareableInterceptedResumeCancellableWith$lambda-0$FUNCTION_REFERENCE$466.invoke#internal + 72
  at 25 shared               0x00000001035f7edc kfun:kotlinx.coroutines.internal.$shareableInterceptedResumeCancellableWith$lambda-0$FUNCTION_REFERENCE$466.$<bridge-UNN>invoke(){}#internal + 68
  at 26 shared               0x000000010345cdd4 WorkerLaunchpad + 192
  at 27 shared               0x0000000103a7a330 _ZN6Worker19processQueueElementEb + 2364
  at 28 shared               0x0000000103a7d980 Kotlin_Worker_processQueueInternal + 116
  at 29 shared               0x000000010345f780 kfun:kotlin.native.concurrent.Worker#processQueue(){}kotlin.Boolean + 60
Caused by: kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen de.comp.proj.shared.ktor.TrackingApi@82553128
  at 0  shared               0x000000010343145c kfun:kotlin.Throwable#<init>(kotlin.String?){} + 96
  at 1  shared               0x0000000103429750 kfun:kotlin.Exception#<init>(kotlin.String?){} + 92
  at 2  shared               0x00000001034299c0 kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 92
  at 3  shared               0x000000010345bca4 kfun:kotlin.native.concurrent.InvalidMutabilityException#<init>(kotlin.String){} + 92
  at 4  shared               0x000000010345d0a0 ThrowInvalidMutabilityException + 336
  at 5  shared               0x0000000103a6404c MutationCheck + 156
  at 6  shared               0x00000001038b7e24 kfun:de.comp.proj.shared.ktor.TrackingApi#<init>(co.touchlab.kermit.Kermit){} + 472
  at 7  shared               0x000000010385d720 kfun:de.comp.proj.shared.coreModule$lambda-8$lambda-2#internal + 652
  at 8  shared               0x0000000103860754 kfun:de.comp.proj.shared.$coreModule$lambda-8$lambda-2$FUNCTION_REFERENCE$65.invoke#internal + 188
  at 9  shared               0x000000010380ded0 kfun:org.koin.core.instance.InstanceFactory#create(org.koin.core.instance.InstanceContext){}1:0 + 860
  at 10 shared               0x000000010380e840 kfun:org.koin.core.instance.SingleInstanceFactory#create(org.koin.core.instance.InstanceContext){}1:0 + 268
  at 11 shared               0x000000010380ed0c kfun:org.koin.core.instance.SingleInstanceFactory.get$lambda-0#internal + 208
  at 12 shared               0x000000010380edb8 kfun:org.koin.core.instance.SingleInstanceFactory.$get$lambda-0$FUNCTION_REFERENCE$244.invoke#internal + 72
  at 13 shared               0x000000010380ef1c kfun:org.koin.core.instance.SingleInstanceFactory.$get$lambda-0$FUNCTION_REFERENCE$244.$<bridge-UNN>invoke(){}#internal + 68
  at 14 shared               0x0000000103829608 kfun:org.koin.mp.KoinPlatformTools#synchronized(kotlin.Any;kotlin.Function0<0:0>){0§<kotlin.Any?>}0:0 + 232
  at 15 shared               0x000000010380eae8 kfun:org.koin.core.instance.SingleInstanceFactory#get(org.koin.core.instance.InstanceContext){}1:0 + 364
  at 16 shared               0x0000000103815cf4 kfun:org.koin.core.registry.InstanceRegistry#resolveInstance(kotlin.String;kotlin.Function0<org.koin.core.parameter.DefinitionParameters>?){0§<kotlin.Any?>}0:0? + 508
  at 17 shared               0x000000010381f090 kfun:org.koin.core.scope.Scope.resolveInstance#internal + 840
  at 18 shared               0x000000010381ecb8 kfun:org.koin.core.scope.Scope#get(kotlin.reflect.KClass<*>;org.koin.core.qualifier.Qualifier?;kotlin.Function0<org.koin.core.parameter.DefinitionParameters>?){0§<kotlin.Any?>}0:0 + 1860
  at 19 shared               0x00000001038d3c38 kfun:de.comp.proj.shared.models.GeofencingModel.<init>$<anonymous>_1-1#internal + 800
  at 20 shared               0x00000001038d4c30 kfun:de.comp.proj.shared.models.GeofencingModel.$<init>$<anonymous>_1-1$FUNCTION_REFERENCE$169.invoke#internal + 152
  at 21 shared               0x000000010345da40 kfun:kotlin.native.concurrent.FreezeAwareLazyImpl.getOrInit#internal + 748
  at 22 shared               0x000000010345e0e4 kfun:kotlin.native.concurrent.FreezeAwareLazyImpl#<get-value>(){}1:0 + 500
  at 23 shared               0x00000001038ce674 kfun:de.comp.proj.shared.models.GeofencingModel.<get-trackingApi>#internal + 276
  at 24 shared               0x00000001038d04f4 kfun:de.comp.proj.shared.models.GeofencingModel.$sendGeofencingDataCOROUTINE$51#invokeSuspend(kotlin.Result<kotlin.Any?>){}kotlin.Any? + 460
  at 25 shared               0x00000001038d095c kfun:de.comp.proj.shared.models.GeofencingModel#sendGeofencingData(kotlin.collections.List<de.comp.proj.shared.entity.TrackingData>){} + 292
  at 26 shared               0x00000001038cfda4 kfun:de.comp.proj.shared.models.GeofencingModel.$saveGeofencingDataCOROUTINE$50#invokeSuspend(kotlin.Result<kotlin.Any?>){}kotlin.Any? + 1668
  at 27 shared               0x000000010345114c kfun:kotlin.coroutines.native.internal.BaseContinuationImpl#resumeWith(kotlin.Result<kotlin.Any?>){} + 560
  at 28 shared               0x00000001035c7fd0 kfun:kotlinx.coroutines.internal#resumeCancellableWith__at__kotlin.coroutines.Continuation<0:0>(kotlin.Result<0:0>;kotlin.Function1<kotlin.Throwable,kotlin.Unit>?){0§<kotlin.Any?>} + 2556
  at 29 shared               0x00000001035c8124 kfun:kotlinx.coroutines.internal#resumeCancellableWith$default__at__kotlin.coroutines.Continuation<0:0>(kotlin.Result<0:0>;kotlin.Function1<kotlin.Throwable,kotlin.Unit>?;kotlin.Int){0§<kotlin.Any?>} + 240
r
Caused by: kotlin.native.concurrent.InvalidMutabilityException
, so something's being touched from a background thread that shouldn't be. Make sure you only interact with Koin and Ktor from the main thread. Often I find this easier to do when using constructor injection instead of
by inject()
.
l
How can I find out, what shouldn't be touched? I wrote the shared code and ios developer the ios code, now I'm not even sure how I can help him find out what shared code doesn't like. How using constructor injection can help with this?
r
Constructor injection helps because it usually means things instantiate less lazily, so you're less likely to get surprised by touching an inject field the first time from the wrong thread. For investigating, add
ensureNeverFrozen()
calls to things that shouldn't cross threads. That way you'll see an exception immediately when it freezes and can see what's causing it, rather than seeing it later when you try to mutate something. eg from KaMPKit: https://github.com/touchlab/KaMPKit/blob/9edc4eeaf6551ff84749fa97623908852d062d33/shared/src/commonMain/kotlin/co/touchlab/kampkit/models/BreedModel.kt#L28-L30
(I want to refactor that class to use constructor injection at some point, but it works out here because it only ever gets touched from the main thread)
l
I have ensureNeverFrozen in GeofencingModel and TrackingApi from my earlier post (it was accidentally split into two posts), just like in KampKit. I think that's exactly why I get InvalidMutabilityException from GeofencingModel. Though I still don't see how it can help me to spot where the threads cross?
r
mutation attempt of frozen de.comp.proj.shared.ktor.TrackingApi
implies that
TrackingApi
is getting frozen. If it has
ensureNeverFrozen
on it you should get a
FreezingException
with a stack trace that indicates what's freezing it that shouldn't be
l
@russhwolf thanks for your help! Using a constructor was a good hint to start with. But investigating further, I found out that the commented part causes the exception, but I have no idea why. When I comment it out, the ios app works fine. Do you have any explanation for it? (I changed the injection of TrackingApi back to by inject() in GeofencingModel, but don't think that it is relevant in this case)
Copy code
class TrackingApi (private val log: Kermit) : TrackingKtorApi {

    private val client = HttpClientProvider().getHttpClient(BASE_URL).config {
        defaultRequest {
            host = BASE_URL
            header(HttpHeaders.ContentType, ContentType.Application.Json)
            url {
                protocol = URLProtocol.HTTPS
            }
        }
        install(JsonFeature) {
            accept(ContentType.Text.Plain)
            val json = Json {
                ignoreUnknownKeys = true
            }
            serializer = KotlinxSerializer(json)
        }

        install(Logging) {
            //this generates an InvalidMutabilityException: mutation attempt of frozen in ios
            /*logger = object : Logger {
                override fun log(message: String) {
                    log.v("Network") { message }
                }
            }*/
            level = LogLevel.ALL
        }

    }
It's also weird, because I use it in other networking apis and it works fine
r
Ah ok I recognize it now. Ktor freezes all the lambdas inside the client config. That touches the
Kermit
instance in the constructor so it gets frozen too. But because it's a
val
it has an implicit
this
reference to the
TrackingApi
so that also gets frozen. You can work around this by not using a constructor property. That way the constructor arg will still be frozen but the containing class won't be. We do that in KaMPKit here: https://github.com/touchlab/KaMPKit/blob/main/shared/src/commonMain/kotlin/co/touchlab/kampkit/ktor/DogApiImpl.kt#L18-L21
K 1
There's also some other mitigation strategies in this youtrack ticket: https://youtrack.jetbrains.com/issue/KTOR-1628
l
That's interesting. Thank you so much!