From 587d424032db6182ae7e009ed9e10c33a1a22183 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 28 Jan 2021 18:44:24 +0300 Subject: [PATCH] Promote CoroutineStart.UNDISPATCHED to non experimental API Fixes #1393 --- .../common/src/CoroutineStart.kt | 12 ++++--- .../test/AtomicCancellationCommonTest.kt | 36 +++++++++++++++++-- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/CoroutineStart.kt b/kotlinx-coroutines-core/common/src/CoroutineStart.kt index c9be183814..6059829c23 100644 --- a/kotlinx-coroutines-core/common/src/CoroutineStart.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineStart.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.coroutines @@ -58,8 +58,8 @@ public enum class CoroutineStart { ATOMIC, /** - * Immediately executes the coroutine until its first suspension point _in the current thread_ as if the - * coroutine was started using [Dispatchers.Unconfined]. However, when the coroutine is resumed from suspension + * Immediately executes the coroutine until its first suspension point _in the current thread_ similarly to + * the coroutine being started using [Dispatchers.Unconfined]. However, when the coroutine is resumed from suspension * it is dispatched according to the [CoroutineDispatcher] in its context. * * This is similar to [ATOMIC] in the sense that coroutine starts executing even if it was already cancelled, @@ -68,9 +68,11 @@ public enum class CoroutineStart { * Cancellability of coroutine at suspension points depends on the particular implementation details of * suspending functions as in [DEFAULT]. * - * **Note: This is an experimental api.** Execution semantics of coroutines may change in the future when this mode is used. + * ### Unconfined event loop + * + * Unlike [Dispatchers.Unconfined] and [MainCoroutineDispatcher.immediate], nested undispatched coroutines do not form + * an event loop that otherwise prevents potential stack overflow in case of unlimited nesting. */ - @ExperimentalCoroutinesApi // Since 1.0.0, no ETA on stability UNDISPATCHED; /** diff --git a/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt b/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt index c763faf225..a41013779a 100644 --- a/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt +++ b/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.coroutines @@ -31,6 +31,38 @@ class AtomicCancellationCommonTest : TestBase() { expect(3) } + @Test + fun testUndispatchedLaunch() = runTest { + expect(1) + assertFailsWith { + withContext(Job()) { + cancel() + launch(start = CoroutineStart.UNDISPATCHED) { + expect(2) + yield() + expectUnreached() + } + } + } + finish(3) + } + + @Test + fun testUndispatchedLaunchWithUnconfinedContext() = runTest { + expect(1) + assertFailsWith { + withContext(Dispatchers.Unconfined + Job()) { + cancel() + launch(start = CoroutineStart.UNDISPATCHED) { + expect(2) + yield() + expectUnreached() + } + } + } + finish(3) + } + @Test fun testDeferredAwaitCancellable() = runTest { expect(1) @@ -122,4 +154,4 @@ class AtomicCancellationCommonTest : TestBase() { yield() // now yield finish(4) } -} \ No newline at end of file +}