Skip to content

Commit 06395bc

Browse files
authored
Update CancellableContinuation documentation (#4268)
1 parent 75e72cd commit 06395bc

File tree

1 file changed

+92
-17
lines changed

1 file changed

+92
-17
lines changed

Diff for: kotlinx-coroutines-core/common/src/CancellableContinuation.kt

+92-17
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,99 @@ import kotlinx.coroutines.internal.*
44
import kotlin.coroutines.*
55
import kotlin.coroutines.intrinsics.*
66

7-
// --------------- cancellable continuations ---------------
8-
97
/**
10-
* Cancellable continuation. It is _completed_ when resumed or cancelled.
11-
* When the [cancel] function is explicitly invoked, this continuation immediately resumes with a [CancellationException] or
8+
* Cancellable [continuation][Continuation] is a thread-safe continuation primitive with the support of
9+
* an asynchronous cancellation.
10+
*
11+
* Cancellable continuation can be [resumed][Continuation.resumeWith], but unlike regular [Continuation],
12+
* it also might be [cancelled][CancellableContinuation.cancel] explicitly or [implicitly][Job.cancel] via a parent [job][Job].
13+
*
14+
* If the continuation is cancelled successfully, it resumes with a [CancellationException] or
1215
* the specified cancel cause.
1316
*
14-
* An instance of `CancellableContinuation` is created by the [suspendCancellableCoroutine] function.
17+
* ### Usage
18+
*
19+
* An instance of `CancellableContinuation` can only be obtained by the [suspendCancellableCoroutine] function.
20+
* The interface itself is public for use and private for implementation.
21+
*
22+
* A typical usages of this function is to suspend a coroutine while waiting for a result
23+
* from a callback or an external source of values that optionally supports cancellation:
24+
*
25+
* ```
26+
* suspend fun <T> CompletableFuture<T>.await(): T = suspendCancellableCoroutine { c ->
27+
* val future = this
28+
* future.whenComplete { result, throwable ->
29+
* if (throwable != null) {
30+
* // Resume continuation with an exception if an external source failed
31+
* c.resumeWithException(throwable)
32+
* } else {
33+
* // Resume continuation with a value if it was computed
34+
* c.resume(result)
35+
* }
36+
* }
37+
* // Cancel the computation if the continuation itself was cancelled because a caller of 'await' is cancelled
38+
* c.invokeOnCancellation { future.cancel(true) }
39+
* }
40+
* ```
41+
*
42+
* ### Thread-safety
43+
*
44+
* Instances of [CancellableContinuation] are thread-safe and can be safely shared across multiple threads.
45+
* [CancellableContinuation] allows concurrent invocations of the [cancel] and [resume] pair, guaranteeing
46+
* that only one of these operations will succeed.
47+
* Concurrent invocations of [resume] methods lead to a [IllegalStateException] and are considered a programmatic error.
48+
* Concurrent invocations of [cancel] methods is permitted, and at most one of them succeeds.
49+
*
50+
* ### Prompt cancellation guarantee
51+
*
52+
* A cancellable continuation provides a **prompt cancellation guarantee**.
53+
*
54+
* If the [Job] of the coroutine that obtained a cancellable continuation was cancelled while this continuation was suspended it will not resume
55+
* successfully, even if [CancellableContinuation.resume] was already invoked but not yet executed.
56+
*
57+
* The cancellation of the coroutine's job is generally asynchronous with respect to the suspended coroutine.
58+
* The suspended coroutine is resumed with a call to its [Continuation.resumeWith] member function or to the
59+
* [resume][Continuation.resume] extension function.
60+
* However, when the coroutine is resumed, it does not immediately start executing but is passed to its
61+
* [CoroutineDispatcher] to schedule its execution when the dispatcher's resources become available for execution.
62+
* The job's cancellation can happen before, after, and concurrently with the call to `resume`. In any
63+
* case, prompt cancellation guarantees that the coroutine will not resume its code successfully.
64+
*
65+
* If the coroutine was resumed with an exception (for example, using the [Continuation.resumeWithException] extension
66+
* function) and cancelled, then the exception thrown by the `suspendCancellableCoroutine` function is determined
67+
* by what happened first: exceptional resume or cancellation.
68+
*
69+
* ### Resuming with a closeable resource
1570
*
16-
* Cancellable continuation has three states (as subset of [Job] states):
71+
* [CancellableContinuation] provides the capability to work with values that represent a resource that should be
72+
* closed. For that, it provides `resume(value: R, onCancellation: ((cause: Throwable, value: R, context: CoroutineContext) -> Unit)`
73+
* function that guarantees that either the given `value` will be successfully returned from the corresponding
74+
* `suspend` function or that `onCancellation` will be invoked with the supplied value:
75+
*
76+
* ```
77+
* continuation.resume(resourceToResumeWith) { _, resourceToClose, _
78+
* // Will be invoked if the continuation is cancelled while being dispatched
79+
* resourceToClose.close()
80+
* }
81+
* ```
82+
*
83+
* #### Continuation states
84+
*
85+
* A cancellable continuation has three observable states:
1786
*
1887
* | **State** | [isActive] | [isCompleted] | [isCancelled] |
1988
* | ----------------------------------- | ---------- | ------------- | ------------- |
2089
* | _Active_ (initial state) | `true` | `false` | `false` |
2190
* | _Resumed_ (final _completed_ state) | `false` | `true` | `false` |
2291
* | _Canceled_ (final _completed_ state)| `false` | `true` | `true` |
2392
*
24-
* Invocation of [cancel] transitions this continuation from _active_ to _cancelled_ state, while
25-
* invocation of [Continuation.resume] or [Continuation.resumeWithException] transitions it from _active_ to _resumed_ state.
26-
*
27-
* A [cancelled][isCancelled] continuation implies that it is [completed][isCompleted].
93+
* For a detailed description of each state, see the corresponding properties' documentation.
2894
*
29-
* Invocation of [Continuation.resume] or [Continuation.resumeWithException] in _resumed_ state produces an [IllegalStateException],
30-
* but is ignored in _cancelled_ state.
95+
* A successful invocation of [cancel] transitions the continuation from an _active_ to a _cancelled_ state, while
96+
* an invocation of [Continuation.resume] or [Continuation.resumeWithException] transitions it from
97+
* an _active_ to _resumed_ state.
3198
*
99+
* Possible state transitions diagram:
32100
* ```
33101
* +-----------+ resume +---------+
34102
* | Active | ----------> | Resumed |
@@ -45,18 +113,24 @@ import kotlin.coroutines.intrinsics.*
45113
@SubclassOptInRequired(InternalForInheritanceCoroutinesApi::class)
46114
public interface CancellableContinuation<in T> : Continuation<T> {
47115
/**
48-
* Returns `true` when this continuation is active -- it has not completed or cancelled yet.
116+
* Returns `true` when this continuation is active -- it was created,
117+
* but not yet [resumed][Continuation.resumeWith] or [cancelled][CancellableContinuation.cancel].
118+
*
119+
* This state implies that [isCompleted] and [isCancelled] are `false`,
120+
* but this can change immediately after the invocation because of parallel calls to [cancel] and [resume].
49121
*/
50122
public val isActive: Boolean
51123

52124
/**
53-
* Returns `true` when this continuation has completed for any reason. A cancelled continuation
54-
* is also considered complete.
125+
* Returns `true` when this continuation was completed -- [resumed][Continuation.resumeWith] or
126+
* [cancelled][CancellableContinuation.cancel].
127+
*
128+
* This state implies that [isActive] is `false`.
55129
*/
56130
public val isCompleted: Boolean
57131

58132
/**
59-
* Returns `true` if this continuation was [cancelled][cancel].
133+
* Returns `true` if this continuation was [cancelled][CancellableContinuation.cancel].
60134
*
61135
* It implies that [isActive] is `false` and [isCompleted] is `true`.
62136
*/
@@ -124,6 +198,7 @@ public interface CancellableContinuation<in T> : Continuation<T> {
124198
/**
125199
* Cancels this continuation with an optional cancellation `cause`. The result is `true` if this continuation was
126200
* cancelled as a result of this invocation, and `false` otherwise.
201+
* [cancel] might return `false` when the continuation was either [resumed][resume] or already [cancelled][cancel].
127202
*/
128203
public fun cancel(cause: Throwable? = null): Boolean
129204

@@ -243,7 +318,7 @@ internal fun <T> CancellableContinuation<T>.invokeOnCancellation(handler: Cancel
243318
/**
244319
* Suspends the coroutine like [suspendCoroutine], but providing a [CancellableContinuation] to
245320
* the [block]. This function throws a [CancellationException] if the [Job] of the coroutine is
246-
* cancelled or completed while it is suspended.
321+
* cancelled or completed while it is suspended, or if [CancellableContinuation.cancel] is invoked.
247322
*
248323
* A typical use of this function is to suspend a coroutine while waiting for a result
249324
* from a single-shot callback API and to return the result to the caller.

0 commit comments

Comments
 (0)