diff --git a/common/kotlinx-coroutines-core-common/src/Builders.common.kt b/common/kotlinx-coroutines-core-common/src/Builders.common.kt index f16679c03c..43c8d42537 100644 --- a/common/kotlinx-coroutines-core-common/src/Builders.common.kt +++ b/common/kotlinx-coroutines-core-common/src/Builders.common.kt @@ -214,7 +214,7 @@ public suspend fun withContext( } } // SLOW PATH -- use new dispatcher - val coroutine = DispatchedCoroutine(newContext, uCont) // MODE_DISPATCHED + val coroutine = DispatchedCoroutine(newContext, uCont) // MODE_ATOMIC_DEFAULT coroutine.initParentJob() block.startCoroutineCancellable(coroutine, coroutine) coroutine.getResult() @@ -292,7 +292,7 @@ private class DispatchedCoroutine( context: CoroutineContext, uCont: Continuation ) : ScopeCoroutine(context, uCont) { - override val defaultResumeMode: Int get() = MODE_CANCELLABLE + override val defaultResumeMode: Int get() = MODE_ATOMIC_DEFAULT // this is copy-and-paste of a decision state machine inside AbstractionContinuation // todo: we may some-how abstract it via inline class diff --git a/common/kotlinx-coroutines-core-common/test/CoroutineScopeTest.kt b/common/kotlinx-coroutines-core-common/test/CoroutineScopeTest.kt index 949dfce6a3..b3d9125de8 100644 --- a/common/kotlinx-coroutines-core-common/test/CoroutineScopeTest.kt +++ b/common/kotlinx-coroutines-core-common/test/CoroutineScopeTest.kt @@ -219,6 +219,30 @@ class CoroutineScopeTest : TestBase() { } } + @Test + fun testCoroutineScopeCancellationVsException() = runTest { + expect(1) + var job: Job? = null + job = launch(start = CoroutineStart.UNDISPATCHED) { + expect(2) + try { + coroutineScope { + expect(3) + yield() // must suspend + expect(5) + job!!.cancel() // cancel this job _before_ it throws + throw TestException1() + } + } catch (e: TestException1) { + // must have caught TextException + expect(6) + } + } + expect(4) + yield() // to coroutineScope + finish(7) + } + @Test fun testScopePlusContext() { assertSame(EmptyCoroutineContext, scopePlusContext(EmptyCoroutineContext, EmptyCoroutineContext)) diff --git a/common/kotlinx-coroutines-core-common/test/SupervisorTest.kt b/common/kotlinx-coroutines-core-common/test/SupervisorTest.kt index b46e811587..c9aaacb979 100644 --- a/common/kotlinx-coroutines-core-common/test/SupervisorTest.kt +++ b/common/kotlinx-coroutines-core-common/test/SupervisorTest.kt @@ -195,6 +195,30 @@ class SupervisorTest : TestBase() { assertTrue(parent.isCancelled) } + @Test + fun testSupervisorScopeCancellationVsException() = runTest { + expect(1) + var job: Job? = null + job = launch(start = CoroutineStart.UNDISPATCHED) { + expect(2) + try { + supervisorScope { + expect(3) + yield() // must suspend + expect(5) + job!!.cancel() // cancel this job _before_ it throws + throw TestException1() + } + } catch (e: TestException1) { + // must have caught TextException + expect(6) + } + } + expect(4) + yield() // to coroutineScope + finish(7) + } + private class TestException1 : Exception() private class TestException2 : Exception() } diff --git a/common/kotlinx-coroutines-core-common/test/WithContextTest.kt b/common/kotlinx-coroutines-core-common/test/WithContextTest.kt index 642b47a855..d56391c6de 100644 --- a/common/kotlinx-coroutines-core-common/test/WithContextTest.kt +++ b/common/kotlinx-coroutines-core-common/test/WithContextTest.kt @@ -139,6 +139,60 @@ class WithContextTest : TestBase() { } } + @Test + fun testRunCancellationUndispatchedVsException() = runTest { + expect(1) + var job: Job? = null + job = launch(start = CoroutineStart.UNDISPATCHED) { + expect(2) + try { + // Same dispatcher, different context + withContext(CoroutineName("testRunCancellationUndispatchedVsException")) { + expect(3) + yield() // must suspend + expect(5) + job!!.cancel() // cancel this job _before_ it throws + throw TestException() + } + } catch (e: TestException) { + // must have caught TextException + expect(6) + } + } + expect(4) + yield() // to coroutineScope + finish(7) + } + + @Test + fun testRunCancellationDispatchedVsException() = runTest { + expect(1) + var job: Job? = null + job = launch(start = CoroutineStart.UNDISPATCHED) { + expect(2) + try { + // "Different" dispatcher (schedules execution back and forth) + withContext(wrapperDispatcher(coroutineContext)) { + expect(4) + yield() // must suspend + expect(6) + job!!.cancel() // cancel this job _before_ it throws + throw TestException() + } + } catch (e: TestException) { + // must have caught TextException + expect(8) + } + } + expect(3) + yield() // withContext is next + expect(5) + yield() // withContext again + expect(7) + yield() // to catch block + finish(9) + } + @Test fun testRunSelfCancellationWithException() = runTest(unhandled = listOf({e -> e is AssertionError})) { expect(1)