Skip to content

Cancelled .tasks crash the app #688

Description

@dfabulich

Run this in Skip Lite. (I think it would repro in Skip Fuse, too, but #687 gets in the way.)

Tap "Push", and quickly tap the back button before the task completes.

struct ContentView: View {
    @State var id: Int = 0
    @State var result: String? = nil

    var body: some View {
        NavigationStack {
            NavigationLink("Push") {
                VStack {
                    Text("Hello, World!")
                    if let result {
                        Text(result)
                    }
                    Button("Increment ID") {
                        id += 1
                    }
                }
                .task(id: id) {
                    let id = self.id
                    result = await Task {
                        try? await Task.sleep(for: .seconds(2))
                        return "Hello, World! \(id)"
                    }.value
                    logger.debug("result: isCancelled: \(Task.isCancelled) result: \(result ?? "nil")")
                }
            }
        }
    }
}

Expected: On iOS, the log fires, saying: "result: isCancelled: true result: Hello, World! 0"

Actual: The app crashes.

FATAL EXCEPTION: main
Process: org.appfair.app.ShowcaseLite, PID: 18218
skip.lib.CancellationError: androidx.compose.runtime.LeftCompositionCancellationException: The coroutine scope left the composition
    at skip.lib.Task.value(Concurrency.kt:33)
    at skip.lib.Task$value$1.invokeSuspend(Unknown Source:14)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
    at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:233)
    at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:175)
    at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:147)
    at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:470)
    at kotlinx.coroutines.CancellableContinuationImpl.cancel(CancellableContinuationImpl.kt:214)
    at kotlinx.coroutines.CancellableContinuationImpl.parentCancelled$kotlinx_coroutines_core(CancellableContinuationImpl.kt:221)
    at kotlinx.coroutines.ChildContinuation.invoke(CancellableContinuationImpl.kt:698)
    at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1604)
    at kotlinx.coroutines.JobSupport.tryMakeCancelling(JobSupport.kt:826)
    at kotlinx.coroutines.JobSupport.makeCancelling(JobSupport.kt:786)
    at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:702)
    at kotlinx.coroutines.JobSupport.parentCancelled(JobSupport.kt:668)
    at kotlinx.coroutines.ChildHandleNode.invoke(JobSupport.kt:1580)
    at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1604)
    at kotlinx.coroutines.JobSupport.tryMakeCancelling(JobSupport.kt:826)
    at kotlinx.coroutines.JobSupport.makeCancelling(JobSupport.kt:786)
    at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:702)
    at kotlinx.coroutines.JobSupport.cancelInternal(JobSupport.kt:663)
    at kotlinx.coroutines.JobSupport.cancel(JobSupport.kt:648)
    at androidx.compose.runtime.LaunchedEffectImpl.onForgotten(Effects.kt:283)
    at androidx.compose.runtime.internal.RememberEventDispatcher.dispatchRememberObservers(RememberEventDispatcher.kt:211)
    at androidx.compose.runtime.CompositionImpl.disposeUnusedMovableContent(Composition.kt:1099)
    at androidx.compose.runtime.Recomposer.discardUnusedMovableContentState(Recomposer.kt:1519)
    at androidx.compose.runtime.Recomposer.access$discardUnusedMovableContentState(Recomposer.kt:159)
    at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend(Recomposer.kt:771)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
    at androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(AndroidUiDispatcher.android.kt:79)
    at androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch(AndroidUiDispatcher.android.kt:41)
    at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.run(AndroidUiDispatcher.android.kt:57)
    at android.os.Handler.handleCallback(Handler.java:1070)
    at android.os.Handler.dispatchMessage(Handler.java:125)
    at android.os.Looper.dispatchMessage(Looper.java:333)
    at android.os.Looper.loopOnce(Looper.java:263)
    at android.os.Looper.loop(Looper.java:367)
    at android.app.ActivityThread.main(ActivityThread.java:9287)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:566)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:929)
    Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [androidx.compose.ui.platform.MotionDurationScaleImpl@a954de9, androidx.compose.runtime.BroadcastFrameClock@a855a6e, androidx.compose.runtime.LaunchedEffectImpl@87d8045, StandaloneCoroutine{Cancelling}@b69350f, AndroidUiDispatcher@290599c]
Caused by: androidx.compose.runtime.LeftCompositionCancellationException: The coroutine scope left the composition

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions