Skip to content

Commit

Permalink
Merge pull request #4042 from Kotlin/dk-doc-improvements
Browse files Browse the repository at this point in the history
Small documentation fixes
  • Loading branch information
dkhalanskyjb committed Feb 15, 2024
2 parents d0dabb9 + 83fa0b4 commit c5a579e
Show file tree
Hide file tree
Showing 29 changed files with 371 additions and 180 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public fun <T> Deferred<T>.asListenableFuture(): ListenableFuture<T> {
*
* This suspend function is cancellable.
*
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* If the [Job] of the current coroutine is cancelled while this suspending function is waiting, this function
* stops waiting for the future and immediately resumes with [CancellationException][kotlinx.coroutines.CancellationException].
*
* This method is intended to be used with one-shot Futures, so on coroutine cancellation, the Future is cancelled as well.
Expand Down
4 changes: 2 additions & 2 deletions integration/kotlinx-coroutines-play-services/src/Tasks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ private fun <T> Task<T>.asDeferredImpl(cancellationTokenSource: CancellationToke
* Awaits the completion of the task without blocking a thread.
*
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* If the [Job] of the current coroutine is cancelled while this suspending function is waiting, this function
* stops waiting for the completion stage and immediately resumes with [CancellationException].
*
* For bi-directional cancellation, an overload that accepts [CancellationTokenSource] can be used.
Expand All @@ -105,7 +105,7 @@ public suspend fun <T> Task<T>.await(): T = awaitImpl(null)
* Awaits the completion of the task that is linked to the given [CancellationTokenSource] to control cancellation.
*
* This suspending function is cancellable and cancellation is bi-directional:
* - If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* - If the [Job] of the current coroutine is cancelled while this suspending function is waiting, this function
* cancels the [cancellationTokenSource] and throws a [CancellationException].
* - If the task is cancelled, then this function will throw a [CancellationException].
*
Expand Down
43 changes: 41 additions & 2 deletions integration/kotlinx-coroutines-slf4j/src/MDCContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,47 @@ public typealias MDCContextMap = Map<String, String>?
* using [MDC.put]. These updates are going to be lost on the next suspension and
* reinstalled to the MDC context that was captured or explicitly specified in
* [contextMap] when this object was created on the next resumption.
* Use `withContext(MDCContext()) { ... }` to capture updated map of MDC keys and values
* for the specified block of code.
*
* For example, the following code will not work as expected:
*
* ```
* launch(MDCContext()) {
* MDC.put("key", "value") // This update will be lost
* delay(100)
* println(MDC.get("key")) // This will print null
* }
* ```
*
* Instead, you should use [withContext] to capture the updated MDC context:
*
* ```
* launch(MDCContext()) {
* MDC.put("key", "value") // This update will be captured
* withContext(MDCContext()) {
* delay(100)
* println(MDC.get("key")) // This will print "value"
* }
* }
* ```
*
* There is no way to implicitly propagate MDC context updates from inside the coroutine to the outer scope.
* You have to capture the updated MDC context and restore it explicitly. For example:
*
* ```
* MDC.put("a", "b")
* val contextMap = withContext(MDCContext()) {
* MDC.put("key", "value")
* withContext(MDCContext()) {
* MDC.put("key2", "value2")
* withContext(MDCContext()) {
* yield()
* MDC.getCopyOfContextMap()
* }
* }
* }
* // contextMap contains: {"a"="b", "key"="value", "key2"="value2"}
* MDC.setContextMap(contextMap)
* ```
*
* @param contextMap the value of [MDC] context map.
* Default value is the copy of the current thread's context map that is acquired via
Expand Down
36 changes: 36 additions & 0 deletions integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,40 @@ class MDCContextTest : TestBase() {
}
}
}

/** Tests that the initially captured MDC context gets restored after suspension. */
@Test
fun testSuspensionsUndoingMdcContextUpdates() = runTest {
MDC.put("a", "b")
withContext(MDCContext()) {
MDC.put("key", "value")
assertEquals("b", MDC.get("a"))
yield()
assertNull(MDC.get("key"))
assertEquals("b", MDC.get("a"))
}
}

/** Tests capturing and restoring the MDC context. */
@Test
fun testRestoringMdcContext() = runTest {
MDC.put("a", "b")
val contextMap = withContext(MDCContext()) {
MDC.put("key", "value")
assertEquals("b", MDC.get("a"))
withContext(MDCContext()) {
assertEquals("value", MDC.get("key"))
MDC.put("key2", "value2")
assertEquals("value2", MDC.get("key2"))
withContext(MDCContext()) {
yield()
MDC.getCopyOfContextMap()
}
}
}
MDC.setContextMap(contextMap)
assertEquals("value2", MDC.get("key2"))
assertEquals("value", MDC.get("key"))
assertEquals("b", MDC.get("a"))
}
}
36 changes: 16 additions & 20 deletions kotlinx-coroutines-core/common/src/Await.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ import kotlin.coroutines.*
* This function is **not** equivalent to `deferreds.map { it.await() }` which fails only when it sequentially
* gets to wait for the failing deferred, while this `awaitAll` fails immediately as soon as any of the deferreds fail.
*
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting,
* this function immediately resumes with [CancellationException].
* There is a **prompt cancellation guarantee**. If the job was cancelled while this function was
* suspended, it will not resume successfully. See [suspendCancellableCoroutine] documentation for low-level details.
* This suspending function is cancellable: if the [Job] of the current coroutine is cancelled while this
* suspending function is waiting, this function immediately resumes with [CancellationException].
* There is a **prompt cancellation guarantee**: even if this function is ready to return the result, but was cancelled
* while suspended, [CancellationException] will be thrown. See [suspendCancellableCoroutine] for low-level details.
*/
public suspend fun <T> awaitAll(vararg deferreds: Deferred<T>): List<T> =
if (deferreds.isEmpty()) emptyList() else AwaitAll(deferreds).await()
Expand All @@ -28,11 +27,10 @@ public suspend fun <T> awaitAll(vararg deferreds: Deferred<T>): List<T> =
* This function is **not** equivalent to `this.map { it.await() }` which fails only when it sequentially
* gets to wait for the failing deferred, while this `awaitAll` fails immediately as soon as any of the deferreds fail.
*
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting,
* this function immediately resumes with [CancellationException].
* There is a **prompt cancellation guarantee**. If the job was cancelled while this function was
* suspended, it will not resume successfully. See [suspendCancellableCoroutine] documentation for low-level details.
* This suspending function is cancellable: if the [Job] of the current coroutine is cancelled while this
* suspending function is waiting, this function immediately resumes with [CancellationException].
* There is a **prompt cancellation guarantee**: even if this function is ready to return the result, but was cancelled
* while suspended, [CancellationException] will be thrown. See [suspendCancellableCoroutine] for low-level details.
*/
public suspend fun <T> Collection<Deferred<T>>.awaitAll(): List<T> =
if (isEmpty()) emptyList() else AwaitAll(toTypedArray()).await()
Expand All @@ -41,23 +39,21 @@ public suspend fun <T> Collection<Deferred<T>>.awaitAll(): List<T> =
* Suspends current coroutine until all given jobs are complete.
* This method is semantically equivalent to joining all given jobs one by one with `jobs.forEach { it.join() }`.
*
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting,
* this function immediately resumes with [CancellationException].
* There is a **prompt cancellation guarantee**. If the job was cancelled while this function was
* suspended, it will not resume successfully. See [suspendCancellableCoroutine] documentation for low-level details.
* This suspending function is cancellable: if the [Job] of the current coroutine is cancelled while this
* suspending function is waiting, this function immediately resumes with [CancellationException].
* There is a **prompt cancellation guarantee**: even if this function is ready to return the result, but was cancelled
* while suspended, [CancellationException] will be thrown. See [suspendCancellableCoroutine] for low-level details.
*/
public suspend fun joinAll(vararg jobs: Job): Unit = jobs.forEach { it.join() }

/**
* Suspends current coroutine until all given jobs are complete.
* This method is semantically equivalent to joining all given jobs one by one with `forEach { it.join() }`.
*
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting,
* this function immediately resumes with [CancellationException].
* There is a **prompt cancellation guarantee**. If the job was cancelled while this function was
* suspended, it will not resume successfully. See [suspendCancellableCoroutine] documentation for low-level details.
* This suspending function is cancellable: if the [Job] of the current coroutine is cancelled while this
* suspending function is waiting, this function immediately resumes with [CancellationException].
* There is a **prompt cancellation guarantee**: even if this function is ready to return the result, but was cancelled
* while suspended, [CancellationException] will be thrown. See [suspendCancellableCoroutine] for low-level details.
*/
public suspend fun Collection<Job>.joinAll(): Unit = forEach { it.join() }

Expand Down
18 changes: 9 additions & 9 deletions kotlinx-coroutines-core/common/src/CancellableContinuation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -238,25 +238,25 @@ public interface CancellableContinuation<in T> : Continuation<T> {
*
* This function provides **prompt cancellation guarantee**.
* If the [Job] of the current coroutine was cancelled while this function was suspended it will not resume
* successfully.
* successfully, even if [CancellableContinuation.resume] was already invoked.
*
* The cancellation of the coroutine's job is generally asynchronous with respect to the suspended coroutine.
* The suspended coroutine is resumed with the call it to its [Continuation.resumeWith] member function or to
* The suspended coroutine is resumed with a call to its [Continuation.resumeWith] member function or to the
* [resume][Continuation.resume] extension function.
* However, when coroutine is resumed, it does not immediately start executing, but is passed to its
* [CoroutineDispatcher] to schedule its execution when dispatcher's resources become available for execution.
* The job's cancellation can happen both before, after, and concurrently with the call to `resume`. In any
* case, prompt cancellation guarantees that the the coroutine will not resume its code successfully.
* The job's cancellation can happen before, after, and concurrently with the call to `resume`. In any
* case, prompt cancellation guarantees that the coroutine will not resume its code successfully.
*
* If the coroutine was resumed with an exception (for example, using [Continuation.resumeWithException] extension
* function) and cancelled, then the resulting exception of the `suspendCancellableCoroutine` function is determined
* by whichever action (exceptional resume or cancellation) that happened first.
* function) and cancelled, then the exception thrown by the `suspendCancellableCoroutine` function is determined
* by what happened first: exceptional resume or cancellation.
*
* ### Returning resources from a suspended coroutine
*
* As a result of a prompt cancellation guarantee, when a closeable resource
* (like open file or a handle to another native resource) is returned from a suspended coroutine as a value
* it can be lost when the coroutine is cancelled. In order to ensure that the resource can be properly closed
* As a result of the prompt cancellation guarantee, when a closeable resource
* (like open file or a handle to another native resource) is returned from a suspended coroutine as a value,
* it can be lost when the coroutine is cancelled. To ensure that the resource can be properly closed
* in this case, the [CancellableContinuation] interface provides two functions.
*
* - [invokeOnCancellation][CancellableContinuation.invokeOnCancellation] installs a handler that is called
Expand Down
14 changes: 7 additions & 7 deletions kotlinx-coroutines-core/common/src/CoroutineScope.kt
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,13 @@ public object GlobalScope : CoroutineScope {

/**
* Creates a [CoroutineScope] and calls the specified suspend block with this scope.
* The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, but overrides
* the context's [Job].
* The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, using the
* [Job] from that context as the parent for a new [Job].
*
* This function is designed for _concurrent decomposition_ of work. When any child coroutine in this scope fails,
* this scope fails and all the rest of the children are cancelled (for a different behavior see [supervisorScope]).
* This function returns as soon as the given block and all its children coroutines are completed.
* A usage example of a scope looks like this:
* this scope fails, cancelling all the other children (for a different behavior, see [supervisorScope]).
* This function returns as soon as the given block and all its child coroutines are completed.
* A usage of a scope looks like this:
*
* ```
* suspend fun showSomeData() = coroutineScope {
Expand All @@ -248,8 +248,8 @@ public object GlobalScope : CoroutineScope {
* 3) If the outer scope of `showSomeData` is cancelled, both started `async` and `withContext` blocks are cancelled.
* 4) If the `async` block fails, `withContext` will be cancelled.
*
* The method may throw a [CancellationException] if the current job was cancelled externally
* or may throw a corresponding unhandled [Throwable] if there is any unhandled exception in this scope
* The method may throw a [CancellationException] if the current job was cancelled externally,
* rethrow the exception thrown by [block], or throw an unhandled [Throwable] if there is one
* (for example, from a crashed coroutine that was started with [launch][CoroutineScope.launch] in this scope).
*/
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R {
Expand Down
46 changes: 34 additions & 12 deletions kotlinx-coroutines-core/common/src/Deferred.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,46 @@ import kotlinx.coroutines.selects.*
public interface Deferred<out T> : Job {

/**
* Awaits for completion of this value without blocking a thread and resumes when deferred computation is complete,
* returning the resulting value or throwing the corresponding exception if the deferred was cancelled.
* Awaits for completion of this value without blocking the thread and returns the resulting value or throws
* the exception if the deferred was cancelled.
*
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
* immediately resumes with [CancellationException].
* There is a **prompt cancellation guarantee**. If the job was cancelled while this function was
* suspended, it will not resume successfully. See [suspendCancellableCoroutine] documentation for low-level details.
* Unless the calling coroutine is cancelled, [await] will return the same result on each invocation:
* if the [Deferred] completed successfully, [await] will return the same value every time;
* if the [Deferred] completed exceptionally, [await] will rethrow the same exception.
*
* This function can be used in [select] invocation with [onAwait] clause.
* Use [isCompleted] to check for completion of this deferred value without waiting.
* This suspending function is itself cancellable: if the [Job] of the current coroutine is cancelled or completed
* while this suspending function is waiting, this function immediately resumes with [CancellationException].
*
* This means that [await] can throw [CancellationException] in two cases:
* - if the coroutine in which [await] was called got cancelled,
* - or if the [Deferred] itself got completed with a [CancellationException].
*
* In both cases, the [CancellationException] will cancel the coroutine calling [await], unless it's caught.
* The following idiom may be helpful to avoid this:
* ```
* try {
* deferred.await()
* } catch (e: CancellationException) {
* currentCoroutineContext().ensureActive() // throws if the current coroutine was cancelled
* processException(e) // if this line executes, the exception is the result of `await` itself
* }
* ```
*
* There is a **prompt cancellation guarantee**: even if this function is ready to return the result, but was cancelled
* while suspended, [CancellationException] will be thrown. See [suspendCancellableCoroutine] for low-level details.
*
* This function can be used in [select] invocations with an [onAwait] clause.
* Use [isCompleted] to check for completion of this deferred value without waiting, and
* [join] to wait for completion without returning the result.
*/
public suspend fun await(): T

/**
* Clause for [select] expression of [await] suspending function that selects with the deferred value when it is
* resolved. The [select] invocation fails if the deferred value completes exceptionally (either fails or
* it cancelled).
* Clause using the [await] suspending function as a [select] clause.
* It selects with the deferred value when the [Deferred] completes.
* If [Deferred] completes with an exception, the whole the [select] invocation fails with the same exception.
* Note that, if [Deferred] completed with a [CancellationException], throwing it may have unintended
* consequences. See [await] for details.
*/
public val onAwait: SelectClause1<T>

Expand Down

0 comments on commit c5a579e

Please sign in to comment.