Promote CoroutineStart.UNDISPATCHED to non experimental API (#2505)

Fixes #1393
diff --git a/kotlinx-coroutines-core/common/src/CoroutineStart.kt b/kotlinx-coroutines-core/common/src/CoroutineStart.kt
index c9be183..6059829 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 @@
     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 @@
      * 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 c763faf..a410137 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
@@ -32,6 +32,38 @@
     }
 
     @Test
+    fun testUndispatchedLaunch() = runTest {
+        expect(1)
+        assertFailsWith<CancellationException> {
+            withContext(Job()) {
+                cancel()
+                launch(start = CoroutineStart.UNDISPATCHED) {
+                    expect(2)
+                    yield()
+                    expectUnreached()
+                }
+            }
+        }
+        finish(3)
+    }
+
+    @Test
+    fun testUndispatchedLaunchWithUnconfinedContext() = runTest {
+        expect(1)
+        assertFailsWith<CancellationException> {
+            withContext(Dispatchers.Unconfined + Job()) {
+                cancel()
+                launch(start = CoroutineStart.UNDISPATCHED) {
+                    expect(2)
+                    yield()
+                    expectUnreached()
+                }
+            }
+        }
+        finish(3)
+    }
+
+    @Test
     fun testDeferredAwaitCancellable() = runTest {
         expect(1)
         val deferred = async { // deferred, not yet complete
@@ -122,4 +154,4 @@
         yield() // now yield
         finish(4)
     }
-}
\ No newline at end of file
+}