Skip to content

Commit 8e247be

Browse files
committed
Document withTimeout*, produce
1 parent f819989 commit 8e247be

File tree

3 files changed

+104
-61
lines changed

3 files changed

+104
-61
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,8 @@ public object GlobalScope : CoroutineScope {
836836
* ```
837837
*
838838
* If cancellation during the acquisition of the resource is also undesired, the following pattern can be used
839-
* ([coroutineScope] is not needed here, as [withContext] defines its own [CoroutineScope], but :
839+
* ([coroutineScope] would be meaningless here, as [withContext] defines its own [CoroutineScope],
840+
* but another lexical scope like [supervisorScope] or [withContext] can be useful as part of this pattern):
840841
*
841842
* ```
842843
* withContext(NonCancellable) {

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

Lines changed: 54 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,11 @@ import kotlin.time.*
1414
import kotlin.time.Duration.Companion.milliseconds
1515

1616
/**
17-
* Runs a given suspending [block] of code inside a coroutine with a specified [timeout][timeMillis] and throws
18-
* a [TimeoutCancellationException] if the timeout was exceeded.
19-
* If the given [timeMillis] is non-positive, [TimeoutCancellationException] is thrown immediately.
17+
* Shortcut for calling [withTimeout] with a [Duration] timeout of [timeMillis] milliseconds.
18+
* Please see that overload for details.
2019
*
21-
* The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
22-
* the cancellable suspending function inside the block throws a [TimeoutCancellationException].
23-
*
24-
* The sibling function that does not throw an exception on timeout is [withTimeoutOrNull].
25-
* Note that the timeout action can be specified for a [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
26-
*
27-
* **The timeout event is asynchronous with respect to the code running in the block** and may happen at any time,
28-
* even right before the return from inside the timeout [block]. Keep this in mind if you open or acquire some
29-
* resource inside the [block] that needs closing or release outside the block.
30-
* See the
31-
* [Asynchronous timeout and resources](https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#asynchronous-timeout-and-resources)
32-
* section of the coroutines guide for details.
33-
*
34-
* > Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher].
35-
*
36-
* @param timeMillis timeout time in milliseconds.
20+
* > Note: the behavior of this function can be different from [withTimeout] if [timeMillis] is greater than
21+
* `Long.MAX_VALUE / 2` milliseconds.
3722
*/
3823
public suspend fun <T> withTimeout(timeMillis: Long, block: suspend CoroutineScope.() -> T): T {
3924
contract {
@@ -52,12 +37,9 @@ public suspend fun <T> withTimeout(timeMillis: Long, block: suspend CoroutineSco
5237
* If the [block] execution times out, it is cancelled with a [TimeoutCancellationException].
5338
* If the [timeout] is non-positive, this happens immediately and the [block] is not executed.
5439
*
55-
* **The timeout event is asynchronous with respect to the code running in the block** and may happen at any time,
56-
* even right before the return from inside the timeout [block]. Keep this in mind if you open or acquire some
57-
* resource inside the [block] that needs closing or release outside the block.
58-
* See the
59-
* [Asynchronous timeout and resources](https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#asynchronous-timeout-and-resources)
60-
* section of the coroutines guide for details.
40+
* The cancellation on timeout is asynchronous with respect to the code running in the block
41+
* and may happen at any time, even after the [block] finishes executing but before the caller gets resumed with
42+
* the result.
6143
*
6244
* > Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher].
6345
*
@@ -160,26 +142,11 @@ public suspend fun <T> withTimeout(timeout: Duration, block: suspend CoroutineSc
160142
}
161143

162144
/**
163-
* Runs a given suspending block of code inside a coroutine with a specified [timeout][timeMillis] and returns
164-
* `null` if this timeout was exceeded.
165-
* If the given [timeMillis] is non-positive, `null` is returned immediately.
166-
*
167-
* The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
168-
* cancellable suspending function inside the block throws a [TimeoutCancellationException].
169-
*
170-
* The sibling function that throws an exception on timeout is [withTimeout].
171-
* Note that the timeout action can be specified for a [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
172-
*
173-
* **The timeout event is asynchronous with respect to the code running in the block** and may happen at any time,
174-
* even right before the return from inside the timeout [block]. Keep this in mind if you open or acquire some
175-
* resource inside the [block] that needs closing or release outside the block.
176-
* See the
177-
* [Asynchronous timeout and resources](https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#asynchronous-timeout-and-resources)
178-
* section of the coroutines guide for details.
179-
*
180-
* > Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher].
145+
* Shortcut for calling [withTimeoutOrNull] with a [Duration] timeout of [timeMillis] milliseconds.
146+
* Please see that overload for details.
181147
*
182-
* @param timeMillis timeout time in milliseconds.
148+
* > Note: the behavior of this function can be different from [withTimeoutOrNull] if [timeMillis] is greater than
149+
* `Long.MAX_VALUE / 2` milliseconds.
183150
*/
184151
public suspend fun <T> withTimeoutOrNull(timeMillis: Long, block: suspend CoroutineScope.() -> T): T? {
185152
if (timeMillis <= 0L) return null
@@ -201,24 +168,54 @@ public suspend fun <T> withTimeoutOrNull(timeMillis: Long, block: suspend Corout
201168
}
202169

203170
/**
204-
* Runs a given suspending block of code inside a coroutine with the specified [timeout] and returns
205-
* `null` if this timeout was exceeded.
206-
* If the given [timeout] is non-positive, `null` is returned immediately.
171+
* Calls the specified suspending [block] with the specified [timeout], suspends until it completes,
172+
* and returns the result.
173+
*
174+
* If the [block] execution times out, it is cancelled with a [TimeoutCancellationException].
175+
* If the [timeout] is non-positive, this happens immediately and the [block] is not executed.
176+
*
177+
* The cancellation on timeout is asynchronous with respect to the code running in the block
178+
* and may happen at any time, even after the [block] finishes executing but before the caller gets resumed with
179+
* the result.
180+
*
181+
* > Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher].
182+
*
183+
* ## Structured Concurrency
184+
*
185+
* [withTimeoutOrNull] behaves like [coroutineScope], as it, too, creates a new *scoped child coroutine*.
186+
* Refer to the documentation of [coroutineScope] for details.
207187
*
208-
* The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
209-
* cancellable suspending function inside the block throws a [TimeoutCancellationException].
188+
* ## Pitfalls
189+
*
190+
* ### Cancellation is cooperative
210191
*
211-
* The sibling function that throws an exception on timeout is [withTimeout].
212-
* Note that the timeout action can be specified for a [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
192+
* [withTimeoutOrNull] will not automatically stop all code inside it from being executed
193+
* once the timeout gets triggered.
194+
* It only cancels the running [block], but it's up to the [block] to notice that it was cancelled, for example,
195+
* using [ensureActive], checking [isActive], or using [suspendCancellableCoroutine].
196+
*
197+
* For example, this JVM code will run to completion, taking 10 seconds to do so:
198+
*
199+
* ```
200+
* withTimeout(1.seconds) {
201+
* Thread.sleep(10_000)
202+
* }
203+
* ```
213204
*
214-
* **The timeout event is asynchronous with respect to the code running in the block** and may happen at any time,
215-
* even right before the return from inside the timeout [block]. Keep this in mind if you open or acquire some
216-
* resource inside the [block] that needs closing or release outside the block.
217-
* See the
218-
* [Asynchronous timeout and resources](https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#asynchronous-timeout-and-resources)
205+
* On the JVM, use the `runInterruptible` function to propagate cancellations
206+
* to blocking JVM code as thread interruptions.
207+
*
208+
* See the [Cancellation is cooperative](https://kotlinlang.org/docs/cancellation-and-timeouts.html#cancellation-is-cooperative).
219209
* section of the coroutines guide for details.
220210
*
221-
* > Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher].
211+
* ### Returning closeable resources
212+
*
213+
* Values returned from [withTimeoutOrNull] will typically be lost if the caller is cancelled.
214+
*
215+
* See the corresponding section in the [coroutineScope] documentation for details.
216+
*
217+
* @see withTimeoutOrNull
218+
* @see SelectBuilder.onTimeout
222219
*/
223220
public suspend fun <T> withTimeoutOrNull(timeout: Duration, block: suspend CoroutineScope.() -> T): T? =
224221
withTimeoutOrNull(timeout.toDelayMillis(), block)

kotlinx-coroutines-core/common/src/channels/Produce.kt

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,17 @@ public suspend fun ProducerScope<*>.awaitClose(block: () -> Unit = {}) {
7575
* and returns a reference to the coroutine as a [ReceiveChannel]. This resulting
7676
* object can be used to [receive][ReceiveChannel.receive] elements produced by this coroutine.
7777
*
78-
* The scope of the coroutine contains the [ProducerScope] interface, which implements
79-
* both [CoroutineScope] and [SendChannel], so that the coroutine can invoke [send][SendChannel.send] directly.
78+
* The receiver of [block] is a [ProducerScope], which implements both [SendChannel] and [CoroutineScope].
79+
* This allows invoking [send][SendChannel.send] directly from the [block] to send elements to the channel
80+
* while treating [block] as a coroutine.
8081
*
8182
* The kind of the resulting channel depends on the specified [capacity] parameter.
8283
* See the [Channel] interface documentation for details.
8384
* By default, an unbuffered channel is created.
8485
* If an invalid [capacity] value is specified, an [IllegalArgumentException] is thrown.
8586
*
87+
* ## Behavior specifics
88+
*
8689
* ### Behavior on termination
8790
*
8891
* The channel is [closed][SendChannel.close] when the coroutine completes.
@@ -132,6 +135,19 @@ public suspend fun ProducerScope<*>.awaitClose(block: () -> Unit = {}) {
132135
* for (value in channel) { println(value) }
133136
* ```
134137
*
138+
* The exception will not be considered uncaught even if the parent coroutine does not react to it
139+
* (for example, because it has a [SupervisorJob]).
140+
* This means that, in the following code, the exception will not be reported anywhere and needs to be handled
141+
* manually by receiving from the resulting channel:
142+
*
143+
* ```
144+
* supervisorScope {
145+
* produce<Int> {
146+
* throw IllegalStateException()
147+
* }
148+
* }
149+
* ```
150+
*
135151
* When the coroutine is cancelled via structured concurrency and not the `cancel` function,
136152
* the channel does not automatically close until the coroutine completes,
137153
* so it is possible that some elements will be sent even after the coroutine is cancelled:
@@ -182,7 +198,36 @@ public suspend fun ProducerScope<*>.awaitClose(block: () -> Unit = {}) {
182198
* If this is unsuitable, please create a [Channel] manually and pass the `onUndeliveredElement` callback to the
183199
* constructor: [Channel(onUndeliveredElement = ...)][Channel].
184200
*
185-
* ### Usage example
201+
* ## Structured concurrency
202+
*
203+
* ### Coroutine context
204+
*
205+
* [produce] creates a *child coroutine* of `this` [CoroutineScope].
206+
*
207+
* See the corresponding subsection in the [launch] documentation for details on how the coroutine context is created.
208+
* In essence, the elements of [context] are combined with the elements of the [CoroutineScope.coroutineContext],
209+
* typically overriding them. It is incorrect to pass a [Job] element there, as this breaks structured concurrency.
210+
*
211+
* ### Interactions between coroutines
212+
*
213+
* The details of structured concurrency are described in the [CoroutineScope] interface documentation.
214+
* Here is a restatement of some main points as they relate to [async]:
215+
*
216+
* - The lifecycle of the parent [CoroutineScope] can not end until this coroutine
217+
* (as well as all its children) completes.
218+
* - If the parent [CoroutineScope] is cancelled, this coroutine is cancelled as well.
219+
* - If this coroutine fails with a non-[CancellationException] exception
220+
* and the parent [CoroutineScope] has a non-supervisor [Job] in its context,
221+
* the parent [Job] is cancelled with this exception.
222+
* - If this coroutine fails with an exception and the parent [CoroutineScope] has a supervisor [Job] or no job at all
223+
* (as is the case with [GlobalScope] or malformed scopes),
224+
* the exception is considered uncaught and is only available through the returned [Channel].
225+
* - The lifecycle of the [CoroutineScope] passed as the receiver to the [block]
226+
* will not end until the [block] completes (or gets cancelled before ever having a chance to run).
227+
* - If the [block] throws a [CancellationException], the coroutine is considered cancelled,
228+
* cancelling all its children in turn, but the parent does not get notified.
229+
*
230+
* ## Usage example
186231
*
187232
* ```
188233
* /* Generate random integers until we find the square root of 9801.

0 commit comments

Comments
 (0)