Skip to content

Commit bc48938

Browse files
committed
Address the review further
1 parent 049b554 commit bc48938

File tree

1 file changed

+52
-71
lines changed

1 file changed

+52
-71
lines changed

kotlinx-coroutines-core/common/src/CoroutineStart.kt

+52-71
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import kotlin.coroutines.*
1111
* to describe when and how the coroutine should be dispatched initially.
1212
*
1313
* This parameter only affects how the coroutine behaves until the code of its body starts executing.
14-
* After that, cancellability and dispatching depend on the implementation details of the invoked suspending functions.
14+
* After that, cancellability and dispatching are defined by the behavior of the invoked suspending functions.
1515
*
1616
* The summary of coroutine start options is:
1717
* - [DEFAULT] immediately schedules the coroutine for execution according to its context.
18-
* - [LAZY] delays the moment of the initial dispatch until the result of the coroutine is awaited.
18+
* - [LAZY] delays the moment of the initial dispatch until the result of the coroutine is needed.
1919
* - [ATOMIC] prevents the coroutine from being cancelled before it starts, ensuring that its code will start
2020
* executing in any case.
2121
* - [UNDISPATCHED] immediately executes the coroutine until its first suspension point _in the current thread_.
@@ -46,7 +46,7 @@ public enum class CoroutineStart {
4646
* // Dispatch the job to execute later.
4747
* // The parent coroutine's dispatcher is inherited by default.
4848
* // In this case, it's the single thread backing `runBlocking`.
49-
* val job = launch { // CoroutineStart.DEFAULT is the launch's default start mode
49+
* launch { // CoroutineStart.DEFAULT is launch's default start mode
5050
* println("3. When the thread is available, we start the coroutine")
5151
* }
5252
* println("2. The thread keeps doing other work after launching the coroutine")
@@ -59,7 +59,7 @@ public enum class CoroutineStart {
5959
* println("1. About to start a coroutine not needing a dispatch.")
6060
* // Dispatch the job to execute.
6161
* // `Dispatchers.Unconfined` is explicitly chosen.
62-
* val job = launch(Dispatchers.Unconfined) { // CoroutineStart.DEFAULT is the launch's default start mode
62+
* launch(Dispatchers.Unconfined) { // CoroutineStart.DEFAULT is the launch's default start mode
6363
* println("2. The body will be executed immediately")
6464
* delay(50.milliseconds) // give up the thread to the outer coroutine
6565
* println("4. When the thread is next available, this coroutine proceeds further")
@@ -109,7 +109,7 @@ public enum class CoroutineStart {
109109
* }
110110
* ```
111111
*
112-
* Behavior of [LAZY] can be described with the following examples:
112+
* The behavior of [LAZY] can be described with the following examples:
113113
*
114114
* ```
115115
* // Example of lazily starting a new coroutine that goes through a dispatch
@@ -160,52 +160,44 @@ public enum class CoroutineStart {
160160
*
161161
* Example:
162162
* ```
163-
* val N_PERMITS = 3
164-
* val semaphore = Semaphore(N_PERMITS)
165-
* try {
166-
* repeat(100) {
167-
* semaphore.acquire()
168-
* if (it != 7) {
169-
* println("Scheduling $it...")
170-
* } else {
171-
* // "randomly" cancel the whole procedure
172-
* cancel()
173-
* }
174-
* launch(Dispatchers.Default, start = CoroutineStart.ATOMIC) {
175-
* println("Entered $it")
176-
* try {
177-
* // this `try` block will be entered in any case because of ATOMIC
178-
* println("Performing the procedure $it")
179-
* delay(10.milliseconds) // may throw due to cancellation
180-
* println("Done with the procedure $it")
181-
* } finally {
182-
* semaphore.release()
183-
* }
184-
* }
185-
* }
186-
* } finally {
187-
* withContext(NonCancellable) {
188-
* repeat(N_PERMITS) { semaphore.acquire() }
189-
* println("All permits were successfully returned!")
163+
* val mutex = Mutex()
164+
*
165+
* mutex.lock() // lock the mutex outside the coroutine
166+
* delay(10.milliseconds) // initial portion of the work, protected by the mutex
167+
* val job = launch(start = CoroutineStart.ATOMIC) {
168+
* // the work must continue in a coroutine, but still under the mutex
169+
* println("Coroutine running!")
170+
* try {
171+
* // this `try` block will be entered in any case because of ATOMIC
172+
* println("Starting task...")
173+
* delay(10.milliseconds) // throws due to cancellation
174+
* println("Finished task.")
175+
* } finally {
176+
* mutex.unlock() // correctly release the mutex
190177
* }
191178
* }
179+
*
180+
* job.cancelAndJoin() // we immediately cancel the coroutine.
181+
* mutex.withLock {
182+
* println("The lock has been returned correctly!")
183+
* }
192184
* ```
193185
*
194186
* Here, we used [ATOMIC] to ensure that a semaphore that was acquired outside of the coroutine does get released
195187
* even if cancellation happens between `acquire()` and `launch`.
196188
* As a result, the semaphore will eventually regain all three permits.
197189
*
198-
* Behavior of [ATOMIC] can be described with the following examples:
190+
* The behavior of [ATOMIC] can be described with the following examples:
199191
*
200192
* ```
201193
* // Example of cancelling atomically started coroutines
202194
* runBlocking {
203195
* println("1. Atomically starting a coroutine that goes through a dispatch.")
204196
* launch(start = CoroutineStart.ATOMIC) {
205-
* check(!isActive) // attempting to suspend will throw
206-
* println("4. A coroutine that went through a dispatch also starts.")
197+
* check(!isActive) // attempting to suspend later will throw
198+
* println("4. The coroutine was cancelled (isActive = $isActive), but starts anyway.")
207199
* try {
208-
* delay(10.milliseconds)
200+
* delay(10.milliseconds) // will throw: the coroutine is cancelled
209201
* println("This code will never run.")
210202
* } catch (e: CancellationException) {
211203
* println("5. Cancellation at later points still works.")
@@ -243,48 +235,32 @@ public enum class CoroutineStart {
243235
*
244236
* Example:
245237
* ```
246-
* runBlocking {
247-
* val channel = Channel<Int>(Channel.RENDEZVOUS)
248-
* var subscribers = 0
249-
* fun CoroutineScope.awaitTickNumber(desiredTickNumber: Int) {
250-
* launch(start = CoroutineStart.UNDISPATCHED) {
251-
* ++subscribers
252-
* try {
253-
* for (tickNumber in channel) {
254-
* if (tickNumber >= desiredTickNumber) {
255-
* println("Tick number $desiredTickNumber reached")
256-
* break
257-
* }
258-
* }
259-
* } finally {
260-
* --subscribers
261-
* }
238+
* var tasks = 0
239+
* repeat(3) {
240+
* launch(start = CoroutineStart.UNDISPATCHED) {
241+
* tasks++
242+
* try {
243+
* println("Waiting for a reply...")
244+
* delay(50.milliseconds)
245+
* println("Got a reply!")
246+
* } finally {
247+
* tasks--
262248
* }
263249
* }
264-
* for (subscriberIndex in 1..10) {
265-
* awaitTickNumber(10 + subscriberIndex * 3)
266-
* }
267-
* // Send the current tick number every 10 milliseconds
268-
* // while there are subscribers
269-
* var i = 0
270-
* // Because of UNDISPATCHED,
271-
* // we know that the subscribers are already initialized,
272-
* // so this number is non-zero initially.
273-
* while (subscribers > 0) {
274-
* channel.trySend(++i)
275-
* delay(10.milliseconds)
276-
* }
250+
* }
251+
* // Because of UNDISPATCHED,
252+
* // we know that the tasks already ran to their first suspension point,
253+
* // so this number is non-zero initially.
254+
* while (tasks > 0) {
255+
* println("currently active: $tasks")
256+
* delay(10.milliseconds)
277257
* }
278258
* ```
279259
*
280260
* Here, we implement a publisher-subscriber interaction, where [UNDISPATCHED] ensures that the
281261
* subscribers do get registered before the publisher first checks if it can stop emitting values due to
282262
* the lack of subscribers.
283263
*
284-
* **Pitfall**: unlike [Dispatchers.Unconfined] and [MainCoroutineDispatcher.immediate], nested undispatched
285-
* coroutines do not form an event loop that otherwise prevents potential stack overflow in case of unlimited
286-
* nesting.
287-
*
288264
* ```
289265
* // Constant usage of stack space
290266
* fun CoroutineScope.factorialWithUnconfined(n: Int): Deferred<Int> =
@@ -311,12 +287,12 @@ public enum class CoroutineStart {
311287
* whereas `factorialWithUndispatched` will lead to `n` recursively nested calls,
312288
* resulting in a stack overflow for large values of `n`.
313289
*
314-
* Behavior of [UNDISPATCHED] can be described with the following examples:
290+
* The behavior of [UNDISPATCHED] can be described with the following examples:
315291
*
316292
* ```
317293
* runBlocking {
318294
* println("1. About to start a new coroutine.")
319-
* val job = launch(Dispatchers.Default, start = CoroutineStart.UNDISPATCHED) {
295+
* launch(Dispatchers.Default, start = CoroutineStart.UNDISPATCHED) {
320296
* println("2. The coroutine is immediately started in the same thread.")
321297
* delay(10.milliseconds)
322298
* println("4. The execution continues in a Dispatchers.Default thread.")
@@ -338,6 +314,11 @@ public enum class CoroutineStart {
338314
* println("4. Execution of the outer coroutine only continues later.")
339315
* }
340316
* ```
317+
*
318+
* **Pitfall**: unlike [Dispatchers.Unconfined] and [MainCoroutineDispatcher.immediate], nested undispatched
319+
* coroutines do not form an event loop that otherwise prevents potential stack overflow in case of unlimited
320+
* nesting.
321+
* See [Dispatchers.Unconfined] for an explanation of event loops.
341322
*/
342323
UNDISPATCHED;
343324

0 commit comments

Comments
 (0)