Kotlin / kotlinx.coroutines

Library support for Kotlin coroutines
Apache License 2.0
12.97k stars 1.84k forks source link

Unexpected freezing of instance returned from coroutine on same thread[mirroring KT-42898] #2395

Closed artdfel closed 3 years ago

artdfel commented 3 years ago

According to kotlinx.coroutines Contributing Guidelines, I'm creating this issue as a mirror for KT-42898, reported by Niklas Therning.

Original issue: https://youtrack.jetbrains.com/issue/KT-42898

Description:

Kotlin Native 1.4.10 with coroutines 1.3.9-native-mt-2. Xcode 12.0.1 and iOS 14 simulator.

I have also tested this against the current HEAD (commit 1b0e196) of the native-mt branch with the same outcome.

I would not expect the SomeClass instance in this piece of code to get frozen:

import kotlinx.coroutines.*
import kotlin.native.concurrent.*
import kotlin.test.*

class SomeClass

class UnexpectedFreezing {
    @Test
    fun doTest() {
        val dispatcher = newSingleThreadContext("thread")
        runBlocking(dispatcher) {
            runCatching {
                val o = withContext(dispatcher) {
                    SomeClass().apply { ensureNeverFrozen() }
                }
                assertFalse(o.isFrozen)
            }.exceptionOrNull()?.printStackTrace()
        }
    }
}
Stack trace:
kotlin.native.concurrent.FreezingException: freezing of SomeClass@3541f1f8 has failed, first blocker is SomeClass@3541f1f8
    at kfun:kotlin.Throwable#<init>(kotlin.String?){} + 93 (kotlin/kotlin/Throwable.kt:23:37)
    at kfun:kotlin.Exception#<init>(kotlin.String?){} + 91 (kotlin/kotlin/Exceptions.kt:23:44)
    at kfun:kotlin.RuntimeException#<init>(kotlin.String?){} + 91 (kotlin/kotlin/Exceptions.kt:34:44)
    at kfun:kotlin.native.concurrent.FreezingException#<init>(kotlin.Any;kotlin.Any){} + 643 (kotlin/kotlin/native/concurrent/Freezing.kt:15:9)
    at ThrowFreezingException + 231 (kotlin/kotlin/native/concurrent/Internal.kt:87:15)
    at FreezeSubgraph + 2815
    at Kotlin_Worker_freezeInternal + 27
    at kfun:kotlin.native.concurrent#freeze@0:0(){0§<kotlin.Any?>}0:0 + 62 (kotlin/kotlin/native/concurrent/Freezing.kt:33:5)
    at kfun:kotlinx.coroutines.JobSupport.tryFinalizeSimpleState#internal + 1380 (JobSupport.kt:298:37)
    at kfun:kotlinx.coroutines.JobSupport.tryMakeCompleting#internal + 809 (JobSupport.kt:868:17)
    at kfun:kotlinx.coroutines.JobSupport#makeCompletingOnce(kotlin.Any?){}kotlin.Any? + 642 (JobSupport.kt:840:30)
    at kfun:kotlinx.coroutines.intrinsics#startUndispatchedOrReturn@kotlinx.coroutines.internal.ScopeCoroutine<0:0>(0:1;kotlin.coroutines.SuspendFunction1<0:1,0:0>){0§<kotlin.Any?>;1§<kotlin.Any?>}kotlin.Any? + 1132 (intrinsics/Undispatched.kt:90:12)
    at kfun:kotlinx.coroutines#withContext(kotlin.coroutines.CoroutineContext;kotlin.coroutines.SuspendFunction1<kotlinx.coroutines.CoroutineScope,0:0>){0§<kotlin.Any?>}0:0 + 1801 (Builders.common.kt:154:33)
    at kfun:UnexpectedFreezing.$doTest$lambda-0COROUTINE$1.invokeSuspend#internal + 1045 (UnexpectedFreezing.kt:16:25)
    at kfun:kotlin.coroutines.native.internal.BaseContinuationImpl#resumeWith(kotlin.Result<kotlin.Any?>){} + 758 (kotlin/kotlin/coroutines/ContinuationImpl.kt:30:39)
    at kfun:kotlinx.coroutines.DispatchedTask#run(){} + 2802 (internal/DispatchedTask.kt:39:50)
    at kfun:kotlinx.coroutines.EventLoopImplBase#processNextEvent(){}kotlin.Long + 838 (EventLoop.common.kt:274:18)
    at kfun:kotlinx.coroutines#runEventLoop(kotlinx.coroutines.EventLoop?;kotlin.Function0<kotlin.Boolean>){} + 911 (Builders.kt:80:40)
    at kfun:kotlinx.coroutines.WorkerCoroutineDispatcherImpl.start$lambda-0#internal + 407 (Workers.kt:49:17)
    at kfun:kotlinx.coroutines.WorkerCoroutineDispatcherImpl.$start$lambda-0$FUNCTION_REFERENCE$35.invoke#internal + 62 (Workers.kt:47:24)
    at kfun:kotlinx.coroutines.WorkerCoroutineDispatcherImpl.$start$lambda-0$FUNCTION_REFERENCE$35.$<bridge-UNN>invoke(){}#internal + 62 (Workers.kt:47:24)
    at WorkerLaunchpad + 183 (kotlin/kotlin/native/concurrent/Internal.kt:69:54)
    at _ZN6Worker19processQueueElementEb + 3135
    at _ZN12_GLOBAL__N_113workerRoutineEPv + 54
    at _pthread_start + 148
    at thread_start + 15
elizarov commented 3 years ago

Can you please explain in more detail why you don't expect it to be frozen? What I see there is that you use withContext(dispatcher) to change execution context from the main thread to another background thread and then transfer the reference to SomeClass() back from the background thread to the main thread. It must be frozen in this case and works just like it is expected to work.

ntherning commented 3 years ago

There's no change in thread. We have the outer call to runBlocking(dispatcher) and then the inner call to withContext(dispatcher). So the same dispatcher in both calls and the thread is the same.

elizarov commented 3 years ago

When you call runBlocking(disptacher) you change from the main thread to the background thread. At this moment the corresponding coroutine becomes shared (frozen) and it recursively affects all its children that you launch inside of it.

ntherning commented 3 years ago

Yeah, makes sense! I was trying to reproduce an issue we saw in our app where the Swift part of the app would call into Kotlin on the main thread. The Kotlin code would launch a new coroutine running on the main thread too. The value this couroutine returned later (still on the main thread) caused FreezingException which was unexpected as everything happened on the main thread.

Anyways, I cannot come up with a sample which reproduces the original issue. And we have refactored our Kotlin code and the issue doesn't happen any longer. You can close this. Thank you for your time!