Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ktor native client fields are frozen #1537

Closed
brendanw opened this issue Dec 31, 2019 · 3 comments
Closed

Ktor native client fields are frozen #1537

brendanw opened this issue Dec 31, 2019 · 3 comments
Assignees
Labels

Comments

@brendanw
Copy link

Ktor Version and Engine Used (client or server and name)
kotlin_version=1.3.61
ktor_version=1.3.0-rc2
coroutines_version=1.3.3-native-mt

Describe the bug
Calling testNetworkCall() on iOS leads to a kotlin.native.concurrent.InvalidMutabilityException

fun testNetworkCall() {
  GlobalScope.launch {
    withContext(Dispatchers.Main) {
      val client = HttpClient { }
      val response = client.get<String> {
        url {
          takeFrom("https://api.basebeta.com")
          encodedPath = "/rankings"
        }
      }
      kprint("rankings response -> $response")
    }
  }
}

Full stacktrace from native

Uncaught Kotlin exception: kotlinx.coroutines.CoroutinesInternalError: Fatal exception in coroutines machinery for DispatchedContinuation[WorkerCoroutineDispatcherImpl@34a6648, Continuation @ $testNetworkCall$lambda-2COROUTINE$10]. Please read KDoc to 'handleFatalException' method and report this incident to maintainers
        at 0   BaseBetaCommonApi                   0x000000010e9154f3 kfun:kotlin.Error.<init>(kotlin.String?;kotlin.Throwable?)kotlin.Error + 115
        at 1   BaseBetaCommonApi                   0x000000010ea1aa93 kfun:kotlinx.coroutines.CoroutinesInternalError.<init>(kotlin.String;kotlin.Throwable)kotlinx.coroutines.CoroutinesInternalError + 115
        at 2   BaseBetaCommonApi                   0x000000010ea7f0ea kfun:kotlinx.coroutines.DispatchedTask.handleFatalException$kotlinx-coroutines-core(kotlin.Throwable?;kotlin.Throwable?) + 938
        at 3   BaseBetaCommonApi                   0x000000010ea7ece0 kfun:kotlinx.coroutines.DispatchedTask.run() + 3568
        at 4   BaseBetaCommonApi                   0x000000010ea15c98 kfun:kotlinx.coroutines.EventLoopImplBase.processNextEvent()ValueType + 792
        at 5   BaseBetaCommonApi                   0x000000010ea9a7c1 kfun:kotlinx.coroutines.runEventLoop$kotlinx-coroutines-core(kotlinx.coroutines.EventLoop?;kotlin.Function0<kotlin.Boolean>) + 881
        at 6   BaseBetaCommonApi                   0x000000010eaa1de2 kfun:kotlinx.coroutines.WorkerCoroutineDispatcherImpl.start$lambda-0#internal + 402
        at 7   BaseBetaCommonApi                   0x000000010eaa1fcb kfun:kotlinx.coroutines.WorkerCoroutineDispatcherImpl.$start$lambda-0$FUNCTION_REFERENCE$137.invoke#internal + 59
        at 8   BaseBetaCommonApi                   0x000000010eaa202b kfun:kotlinx.coroutines.WorkerCoroutineDispatcherImpl.$start$lambda-0$FUNCTION_REFERENCE$137.$<bridge-UNN>invoke()#internal + 59
        at 9   BaseBetaCommonApi                   0x000000010e94a5c1 WorkerLaunchpad + 177
        at 10  BaseBetaCommonApi                   0x000000010ed43999 _ZN6Worker19processQueueElementEb + 2569
        at 11  BaseBetaCommonApi                   0x000000010ed43f46 _ZN12_GLOBAL__N_113workerRoutineEPv + 54
        at 12  libsystem_pthread.dylib             0x00007fff51bfe2eb _pthread_body + 126
        at 13  libsystem_pthread.dylib             0x00007fff51c01249 _pthread_start + 66
        at 14  libsystem_pthread.dylib             0x00007fff51bfd40d thread_start + 13
Caused by: kotlin.native.concurrent.InvalidMutabilityException: mutation attempt of frozen io.ktor.utils.io.core.ByteReadPacket@1a78558
        at 0   BaseBetaCommonApi                   0x000000010e91bab7 kfun:kotlin.Throwable.<init>(kotlin.String?)kotlin.Throwable + 87
        at 1   BaseBetaCommonApi                   0x000000010e914e15 kfun:kotlin.Exception.<init>(kotlin.String?)kotlin.Exception + 85
        at 2   BaseBetaCommonApi                   0x000000010e914955 kfun:kotlin.RuntimeException.<init>(kotlin.String?)kotlin.RuntimeException + 85
        at 3   BaseBetaCommonApi                   0x000000010e949225 kfun:kotlin.native.concurrent.InvalidMutabilityException.<init>(kotlin.String)kotlin.native.concurrent.InvalidMutabilityException + 85
        at 4   BaseBetaCommonApi                   0x000000010e94a9f8 ThrowInvalidMutabilityException + 680
        at 5   BaseBetaCommonApi                   0x000000010ed3ab68 MutationCheck + 104
        at 6   BaseBetaCommonApi                   0x000000010ead302c kfun:io.ktor.utils.io.core.AbstractInput.<set-_head>#internal + 188
        at 7   BaseBetaCommonApi                   0x000000010ead5adf kfun:io.ktor.utils.io.core.AbstractInput.steal$ktor-io()io.ktor.utils.io.core.internal.ChunkBuffer? + 623
        at 8   BaseBetaCommonApi                   0x000000010eaea2d1 kfun:io.ktor.utils.io.core.AbstractOutput.writePacket(io.ktor.utils.io.core.ByteReadPacket;kotlin.Long) + 529
        at 9   BaseBetaCommonApi                   0x000000010eab990b kfun:io.ktor.utils.io.ByteChannelSequentialBase.$readRemainingCOROUTINE$69.invokeSuspend(kotlin.Result<kotlin.Any?>)kotlin.Any? + 939
        at 10  BaseBetaCommonApi                   0x000000010eab9efc kfun:io.ktor.utils.io.ByteChannelSequentialBase.readRemaining(kotlin.Long;kotlin.Int)io.ktor.utils.io.core.ByteReadPacket + 316
        at 11  BaseBetaCommonApi                   0x000000010eaccda5 kfun:io.ktor.utils.io.readRemaining@io.ktor.utils.io.ByteReadChannel.()io.ktor.utils.io.core.ByteReadPacket + 261
        at 12  BaseBetaCommonApi                   0x000000010ebe3fd4 kfun:io.ktor.client.features.HttpPlainText.Feature.$install$lambda-1COROUTINE$39.invokeSuspend#internal + 1892
        at 13  BaseBetaCommonApi                   0x000000010ebe4a68 kfun:io.ktor.client.features.HttpPlainText.Feature.$install$lambda-1COROUTINE$39.invoke#internal + 312
        at 14  BaseBetaCommonApi                   0x000000010eb83fe5 kfun:io.ktor.util.pipeline.SuspendFunctionGun.loop#internal + 1141
        at 15  BaseBetaCommonApi                   0x000000010eb8362b kfun:io.ktor.util.pipeline.SuspendFunctionGun.proceed#internal + 395
        at 16  BaseBetaCommonApi                   0x000000010eb83825 kfun:io.ktor.util.pipeline.SuspendFunctionGun.proceedWith#internal + 213
        at 17  BaseBetaCommonApi                   0x000000010ebdfa02 kfun:io.ktor.client.features.HttpCallValidator.Companion.$install$lambda-1COROUTINE$35.invokeSuspend#internal + 1842
        at 18  BaseBetaCommonApi                   0x000000010ebdfff8 kfun:io.ktor.client.features.HttpCallValidator.Companion.$install$lambda-1COROUTINE$35.invoke#internal + 312
        at 19  BaseBetaCommonApi                   0x000000010eb83fe5 kfun:io.ktor.util.pipeline.SuspendFunctionGun.loop#internal + 1141
        at 20  BaseBetaCommonApi                   0x000000010eb8362b kfun:io.ktor.util.pipeline.SuspendFunctionGun.proceed#internal + 395
        at 21  BaseBetaCommonApi                   0x000000010eb83a8a kfun:io.ktor.util.pipeline.SuspendFunctionGun.execute#internal + 474
        at 22  BaseBetaCommonApi                   0x000000010eb7e3c9 kfun:io.ktor.util.pipeline.Pipeline.execute(#GENERIC_kotlin.Any;#GENERIC_kotlin.Any)#GENERIC_kotlin.Any + 361
        at 23  BaseBetaCommonApi                   0x000000010ebc6ea3 kfun:io.ktor.client.call.HttpClientCall.$receiveCOROUTINE$25.invokeSuspend(kotlin.Result<kotlin.Any?>)kotlin.Any? + 2035
        at 24  BaseBetaCommonApi                   0x000000010ebc74f4 kfun:io.ktor.client.call.HttpClientCall.receive(io.ktor.client.call.TypeInfo)kotlin.Any + 308
        at 25  BaseBetaCommonApi                   0x000000010ec8c0ea kfun:com.basebeta.rankings.$testNetworkCall$lambda-2$lambda-1COROUTINE$9.invokeSuspend#internal + 8282
        at 26  BaseBetaCommonApi                   0x000000010e93d0e8 kfun:kotlin.coroutines.native.internal.BaseContinuationImpl.resumeWith(kotlin.Result<kotlin.Any?>) + 712
        at 27  BaseBetaCommonApi                   0x000000010eb84692 kfun:io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith#internal + 930
        at 28  BaseBetaCommonApi                   0x000000010eb84213 kfun:io.ktor.util.pipeline.SuspendFunctionGun.loop#internal + 1699
        at 29  BaseBetaCommonApi                   0x000000010eb860f1 kfun:io.ktor.util.pipeline.SuspendFunctionGun.object-1.resumeWith#internal + 353

I think it is because singletons are by default frozen, so any singleton without the @ThreadLocal annotation will freeze objects it references.

To Reproduce
Copy and paste the code snipped above into a multiplatform project. Run the function from the iOS app.

Expected behavior
We should be able to make an http request without the mutability exception being thrown.

Screenshots
No screenshot to attach.

@brendanw brendanw added the bug label Dec 31, 2019
@cy6erGn0m
Copy link
Contributor

It looks like a byte channel is frozen for some reason in spite of that it is running in the main dispatcher.

@brendanw
Copy link
Author

brendanw commented Jan 1, 2020

Some folks helped me out on the kotlin slack. Based on discussions there I think this is really a case for the kotlin native concurrency documentation to be improved. I opened a ticket there

JetBrains/kotlin-native#3740

@brendanw brendanw closed this as completed Jan 1, 2020
@kpgalligan
Copy link

It's a little more complicated. I've been meaning to comment on the coroutines PR, as I think this is more of an issue for the coroutines implementation. Here's a minimal failing example:

fun goMain() = mainScope.launch {
    val result = withContext(Dispatchers.Main){
        SomeData2("236")
    }

    println("Result from main $result isFrozen ${result.isFrozen}")
}

fun goBackground() = mainScope.launch{
    val result = withContext(Dispatchers.Default){
        SomeData2("125")
    }

    println("Result from default $result isFrozen ${result.isFrozen}")
}

data class SomeData2(val s:String)

From iOS, if you call goMain() first, you can call it over and over and the result value is not frozen. If you then call goBackground() once, and call goMain() after, result wiil be frozen from then on. Internally, the coroutine "machinery" is using FreezableAtomicReference. Values in it are unfrozen, until the container is frozen, and from then on the values are frozen.

To work around this, I create 2 scopes for the coroutines. One that does background thread stuff, and one for ktor.

internal val mainScope = MainScope(Dispatchers.Main)
internal val ktorScope = MainScope(Dispatchers.Main)

I would think coroutines should act consistently (IE not freeze same-thread, or always freeze). I would also add ensureNeverFrozen() to ktor's internals if they're never going to be freezable. My 2 cents. I don't think this is a documentation issue for Kotlin Native so much as the coroutines implementation of it, and (probably?) ktor somewhere, but in general, the documentation around state in native needs to be improved. It's like my calling in life.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants