From 9f97a458b3c6f36a2a119e23219d7973792e8006 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Tue, 13 May 2025 15:25:20 +0200 Subject: [PATCH 1/3] Introduce deprecated coroutine builder overloads accepting a `Job` This is a small step towards deprecating the practice of breaking structured concurrency by passing a `Job` to a coroutine builder. See #3670 --- .../src/ListenableFuture.kt | 19 ++ .../common/src/Builders.common.kt | 23 ++ .../common/src/Guidance.kt | 295 ++++++++++++++++++ .../common/src/channels/Deprecated.kt | 137 ++++++++ .../concurrent/src/Builders.concurrent.kt | 20 ++ kotlinx-coroutines-core/js/src/Guidance.kt | 24 ++ .../jvm/src/GuidanceJvm.kt | 23 ++ .../jvm/src/future/Future.kt | 19 ++ .../wasmJs/src/Guidance.kt | 24 ++ .../kotlinx-coroutines-reactor/src/Convert.kt | 2 + .../kotlinx-coroutines-rx2/src/RxConvert.kt | 3 + .../kotlinx-coroutines-rx3/src/RxConvert.kt | 3 + 12 files changed, 592 insertions(+) create mode 100644 kotlinx-coroutines-core/js/src/Guidance.kt create mode 100644 kotlinx-coroutines-core/jvm/src/GuidanceJvm.kt create mode 100644 kotlinx-coroutines-core/wasmJs/src/Guidance.kt diff --git a/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt b/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt index ea9addc68a..604eb4826b 100644 --- a/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt +++ b/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt @@ -54,6 +54,25 @@ public fun CoroutineScope.future( return coroutine.future } +/** + * Deprecated version of [future] that accepts a [Job]. + * + * See the documentation for the non-deprecated [future] function to learn about the functionality of this function. + * + * See the documentation for the deprecated [async] overload accepting a [Job] for an explanation of the reason + * this pattern is deprecated and the list of possible alternatives. + */ +@Deprecated( + "Passing a Job to coroutine builders breaks structured concurrency, leading to hard-to-diagnose errors. " + + "This pattern should be avoided. " + + "This overload will be deprecated with an error in the future.", + level = DeprecationLevel.WARNING) +public fun CoroutineScope.future( + context: Job, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> T +): ListenableFuture = future(context as CoroutineContext, start, block) + /** * Returns a [Deferred] that is completed or failed by `this` [ListenableFuture]. * diff --git a/kotlinx-coroutines-core/common/src/Builders.common.kt b/kotlinx-coroutines-core/common/src/Builders.common.kt index 23ef7665b5..6d92265bf8 100644 --- a/kotlinx-coroutines-core/common/src/Builders.common.kt +++ b/kotlinx-coroutines-core/common/src/Builders.common.kt @@ -136,6 +136,29 @@ private class LazyDeferredCoroutine( * The cancellation behaviour described above is enabled if and only if the dispatcher is being changed. * For example, when using `withContext(NonCancellable) { ... }` there is no change in dispatcher and * this call will not be cancelled neither on entry to the block inside `withContext` nor on exit from it. + * + * It is incorrect to pass any instances of [Job] other than [NonCancellable] to [withContext]. + * This does not raise an exception only for preserving backward compatibility. + * If the purpose of passing a [Job] to this function is to ensure that [block] gets cancelled when that job + * gets cancelled, this pattern can be used instead: + * + * ``` + * val deferred = scopeWithTheRequiredJob.async { + * withContext(extraContextWithoutJob) { + * // your code here + * } + * } + * try { + * deferred.await() + * } finally { + * // if `await` fails because the current job is cancelled, + * // also cancel the now-unnecessary computations + * deferred.cancel() + * } + * ``` + * + * This way, the [block] gets cancelled when either the caller or `scopeWithTheRequiredJob` gets cancelled, + * ensuring that unnecessary computations do not keep executing */ public suspend fun withContext( context: CoroutineContext, diff --git a/kotlinx-coroutines-core/common/src/Guidance.kt b/kotlinx-coroutines-core/common/src/Guidance.kt index 455ab9153d..bdbd20b8f5 100644 --- a/kotlinx-coroutines-core/common/src/Guidance.kt +++ b/kotlinx-coroutines-core/common/src/Guidance.kt @@ -20,6 +20,152 @@ public fun launch( throw UnsupportedOperationException("Should never be called, was introduced to help with incomplete code") } +/** + * Deprecated version of [launch] that accepts a [Job]. + * + * See the documentation for the non-deprecated [launch] function to learn about the functionality of this function. + * This piece of documentation explains why this overload is deprecated. + * + * It is incorrect to pass a [Job] as [context] to [launch] or [async], because this violates structured concurrency. + * The passed [Job] becomes the sole parent of the newly created coroutine, which completely severs the tie between + * the new coroutine and the [CoroutineScope] in which it is launched. + * + * Structured concurrency ensures that + * - Cancellation of the parent job cancels the children as well, + * which helps avoid unnecessary computations when they are no longer needed. + * - Cancellation of children also can be necessary for reliability: + * if the [CoroutineScope]'s lifecycle is bound to some component that may not be used after it's destroyed, + * performing computations after the parent [CoroutineScope] is cancelled may lead to crashes. + * - For concurrent decomposition of work (when the [CoroutineScope] contains a non-[supervisor][SupervisorJob] job), + * failure of the newly created coroutine also causes the sibling coroutines to fail, + * improving the responsiveness of the program: + * unnecessary computations will not proceed when it's obvious that they are not needed. + * - The [CoroutineScope] can only complete when all its children complete. + * If the [CoroutineScope] is lexically scoped (for example, created by [coroutineScope], [supervisorScope], + * or [withContext]), this means that + * the lexical scope will only be exited (and the calling function will finish) once all child coroutines complete. + * + * ## Possible alternatives + * + * ### Avoiding cancellation + * + * Sometimes, it is undesirable for the child coroutine to react to the cancellation of the parent: for example, + * some computations have to be performed either way. + * `async(NonCancellable)` or `async(Job())` can be used to achieve this effect. + * + * An alternative approach that preserves structured concurrency is this: + * + * ``` + * scope.launch(start = CoroutineStart.ATOMIC) { + * withContext(NonCancellable) { + * // this line will be reached even if the parent is cancelled + * } + * } + * ``` + * + * This way, the child coroutine is guaranteed to complete, + * but the scope is still aware of the child and can track its lifecycle. + * + * ### Avoid cancelling other children + * + * Occasionally, the failure of one child does not mean the work of the other children is also unneeded. + * `launch(Job()) { failure() }` makes sure that the only effect of `failure()` is to make *this* `async` finish + * with an error, while the other coroutines continue executing. + * + * If *all* coroutines in a scope should fail independently, this suggests that the scope + * is a [*supervisor*][supervisorScope]: + * + * ``` + * withContext(CoroutineExceptionHandler { _, e -> + * println("Failure($e)") + * }) { + * supervisorScope { + * val coroutines = List(10) { + * launch { + * delay(10.milliseconds * it) + * throw IllegalStateException("$it is tired of all this") + * } + * } + * coroutines.joinAll() + * } + * } + * ``` + * + * Every coroutine here will run to completion and will fail with its own error. + * + * For non-lexically-scoped [CoroutineScope] instances, + * use [SupervisorJob] instead of [Job] when constructing the [CoroutineScope]. + * + * If only *some* coroutines need to individually have their failures invisible to others + * in a non-lexically-scoped [CoroutineScope], + * the correct approach from the point of view of structured concurrency is this: + * + * ``` + * val childSupervisorScope = CoroutineScope( + * scope.coroutineContext + + * SupervisorJob(scope.coroutineContext.job) + + * CoroutineExceptionHandler { _, e -> + * // process errors + * } + * ) + * childSupervisorScope.launch { + * // failures in this coroutine will not affect other children + * } + * ``` + * + * For a lexically scoped [CoroutineScope], using a [supervisorScope] at the end of the outer scope may help: + * + * ``` + * coroutineScope { + * val deferred = async { + * // failures in this coroutine will affect everyone + * } + * supervisorScope { + * val deferred = launch( + * CoroutineExceptionHandler { _, e -> + * // some individual mechanism of processing exceptions + * } + * ) { + * // failures in this coroutine + * // are only available through `deferred` + * } + * } + * // this line will only be reached when `launch` completes + * } + * // this line will be reached when both `async` and `launch` complete + * ``` + * + * All of these approaches preserve the ability of a parent to cancel the children and to wait for their completion. + * + * ### Avoiding both cancelling and being cancelled + * + * Sometimes, coroutines to be spawned are just completely unrelated to the [CoroutineScope] used as the receiver, + * and no structured concurrency mechanisms are needed. + * + * In that case, [GlobalScope] is the semantically clearer way of expressing opting out of structured concurrency: + * + * ``` + * GlobalScope.launch(CoroutineExceptionHandler { _, e -> + * /* what to do if this coroutine fails */ + * }) { + * // this is explicitly a rogue computation + * } + * ``` + * + * The reason why [GlobalScope] is marked as [delicate][DelicateCoroutinesApi] is exactly that the coroutines + * created in it are outside structured concurrency. + */ +@Deprecated( + "Passing a Job to coroutine builders breaks structured concurrency, leading to hard-to-diagnose errors. " + + "This pattern should be avoided. " + + "This overload will be deprecated with an error in the future.", + level = DeprecationLevel.WARNING) +public fun CoroutineScope.launch( + context: Job, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> Unit +): Job = launch(context as CoroutineContext, start, block) + /** * @suppress this is a function that should help users who are trying to use 'launch' * without the corresponding coroutine scope. It is not supposed to be called. @@ -36,3 +182,152 @@ public fun async( ): Deferred { throw UnsupportedOperationException("Should never be called, was introduced to help with incomplete code") } + +/** + * Deprecated version of [async] that accepts a [Job]. + * + * See the documentation for the non-deprecated [async] function to learn about the functionality of this function. + * This piece of documentation explains why this overload is deprecated. + * + * It is incorrect to pass a [Job] as [context] to [async] or [launch], because this violates structured concurrency. + * The passed [Job] becomes the sole parent of the newly created coroutine, which completely severs the tie between + * the new coroutine and the [CoroutineScope] in which it is launched. + * + * Structured concurrency ensures that + * - Cancellation of the parent job cancels the children as well, + * which helps avoid unnecessary computations when they are no longer needed. + * - Cancellation of children also can be necessary for reliability: + * if the [CoroutineScope]'s lifecycle is bound to some component that may not be used after it's destroyed, + * performing computations after the parent [CoroutineScope] is cancelled may lead to crashes. + * - For concurrent decomposition of work (when the [CoroutineScope] contains a non-[supervisor][SupervisorJob] job), + * failure of the newly created coroutine also causes the sibling coroutines to fail, + * improving the responsiveness of the program: + * unnecessary computations will not proceed when it's obvious that they are not needed. + * - The [CoroutineScope] can only complete when all its children complete. + * If the [CoroutineScope] is lexically scoped (for example, created by [coroutineScope], [supervisorScope], + * or [withContext]), this means that + * the lexical scope will only be exited (and the calling function will finish) once all child coroutines complete. + * + * ## Possible alternatives + * + * ### Avoiding cancellation + * + * Sometimes, it is undesirable for the child coroutine to react to the cancellation of the parent: for example, + * some computations have to be performed either way. + * `async(NonCancellable)` or `async(Job())` can be used to achieve this effect. + * + * Alternative approaches that preserve structured concurrency are this: + * + * ``` + * // Guarantees the completion, but not the delivery of the value + * scope.async(start = CoroutineStart.ATOMIC) { + * withContext(NonCancellable) { + * // this line will be reached even if the parent is cancelled + * } + * // note: the cancellation exception *can* be thrown here, + * // losing the computed value! + * } + * + * // Guarantees the delivery of the value, but is more complex + * val asyncResult = CompletableDeferred() + * scope.launch(start = CoroutineStart.ATOMIC) { + * withContext(NonCancellable) { + * asyncResult.completeWith( + * runCatching { + * // compute the value + * } + * ) + * } + * } + * ``` + * + * This way, the child coroutine is guaranteed to complete, + * but the scope is still aware of the child and can track its lifecycle. + * + * ### Avoid cancelling other children + * + * Occasionally, the failure of one child does not mean the work of the other children is also unneeded. + * `async(Job()) { failure() }` makes sure that the only effect of `failure()` is to make *this* `async` finish + * with an error, while the other coroutines continue executing. + * + * If *all* coroutines in a scope should fail independently, this suggests that the scope + * is a [*supervisor*][supervisorScope]: + * + * ``` + * supervisorScope { + * val coroutines = List(10) { + * async { + * delay(10.milliseconds * it) + * throw IllegalStateException("$it is tired of all this") + * } + * } + * coroutines.forEach { + * println(runCatching { it.await() }) + * } + * } + * ``` + * + * Every coroutine here will run to completion and will fail with its own error. + * + * For non-lexically-scoped [CoroutineScope] instances, + * use [SupervisorJob] instead of [Job] when constructing the [CoroutineScope]. + * + * If only *some* coroutines need to individually have their failures invisible to others + * in a non-lexically-scoped [CoroutineScope], + * the correct approach from the point of view of structured concurrency is this: + * + * ``` + * val childSupervisorScope = CoroutineScope( + * scope.coroutineContext + SupervisorJob(scope.coroutineContext.job) + * ) + * childSupervisorScope.async { + * // failures in this coroutine will not affect other children + * } + * ``` + * + * For a lexically scoped [CoroutineScope], using a [supervisorScope] at the end of the outer scope may help: + * + * ``` + * coroutineScope { + * launch { + * // failures in this coroutine will affect everyone + * } + * supervisorScope { + * val deferred = async { + * // failures in this coroutine + * // are only available through `deferred` + * } + * } + * // this line will only be reached when `async` completes + * } + * // this line will be reached when both `launch` and `async` complete + * ``` + * + * All of these approaches preserve the ability of a parent to cancel the children and to wait for their completion. + * + * ### Avoiding both cancelling and being cancelled + * + * Sometimes, coroutines to be spawned are just completely unrelated to the [CoroutineScope] used as the receiver, + * and no structured concurrency mechanisms are needed. + * + * In that case, [GlobalScope] is the semantically clearer way of expressing opting out of structured concurrency: + * + * ``` + * GlobalScope.async { + * // this is explicitly a rogue computation + * } + * ``` + * + * The reason why [GlobalScope] is marked as [delicate][DelicateCoroutinesApi] is exactly that the coroutines + * created in it are outside structured concurrency. + */ +@Deprecated( + "Passing a Job to coroutine builders breaks structured concurrency, leading to hard-to-diagnose errors. " + + "This pattern should be avoided. " + + "This overload will be deprecated with an error in the future.", + level = DeprecationLevel.WARNING) +public fun CoroutineScope.async( + context: Job, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> T +): Deferred = async(context as CoroutineContext, start, block) diff --git a/kotlinx-coroutines-core/common/src/channels/Deprecated.kt b/kotlinx-coroutines-core/common/src/channels/Deprecated.kt index 463adcb598..149bf3afa4 100644 --- a/kotlinx-coroutines-core/common/src/channels/Deprecated.kt +++ b/kotlinx-coroutines-core/common/src/channels/Deprecated.kt @@ -506,3 +506,140 @@ internal fun ReceiveChannel.zip( internal fun ReceiveChannel<*>.consumes(): CompletionHandler = { cause: Throwable? -> cancelConsumed(cause) } + +/** + * Deprecated version of [produce] that accepts a [Job]. + * + * See the documentation for the non-deprecated [produce] function to learn about the functionality of this function. + * This piece of documentation explains why this overload is deprecated. + * + * It is incorrect to pass a [Job] as [context] to [produce], [async], or [launch], + * because this violates structured concurrency. + * The passed [Job] becomes the sole parent of the newly created coroutine, which completely severs the tie between + * the new coroutine and the [CoroutineScope] in which it is launched. + * + * Structured concurrency ensures that + * - Cancellation of the parent job cancels the children as well, + * which helps avoid unnecessary computations when they are no longer needed. + * - Cancellation of children also can be necessary for reliability: + * if the [CoroutineScope]'s lifecycle is bound to some component that may not be used after it's destroyed, + * performing computations after the parent [CoroutineScope] is cancelled may lead to crashes. + * - For concurrent decomposition of work (when the [CoroutineScope] contains a non-[supervisor][SupervisorJob] job), + * failure of the newly created coroutine also causes the sibling coroutines to fail, + * improving the responsiveness of the program: + * unnecessary computations will not proceed when it's obvious that they are not needed. + * - The [CoroutineScope] can only complete when all its children complete. + * If the [CoroutineScope] is lexically scoped (for example, created by [coroutineScope], [supervisorScope], + * or [withContext]), this means that + * the lexical scope will only be exited (and the calling function will finish) once all child coroutines complete. + * + * ## Possible alternatives + * + * ### Avoiding cancellation + * + * Sometimes, it is undesirable for the child coroutine to react to the cancellation of the parent: for example, + * some computations have to be performed either way. + * `produce(NonCancellable)` or `produce(Job())` can be used to achieve this effect. + * + * Alternative approaches that preserve structured concurrency are this: + * + * ``` + * // Guarantees the completion + * scope.produce(start = CoroutineStart.ATOMIC) { + * withContext(NonCancellable) { + * // this line will be reached even if the parent is cancelled + * } + * } + * ``` + * + * This way, the child coroutine is guaranteed to complete, + * but the scope is still aware of the child and can track its lifecycle. + * + * ### Avoid cancelling other children + * + * Occasionally, the failure of one child does not mean the work of the other children is also unneeded. + * `producer(Job()) { failure() }` makes sure that the only effect of `failure()` is to make *this* `producer` finish + * with an error, while the other coroutines continue executing. + * + * If *all* coroutines in a scope should fail independently, this suggests that the scope + * is a [*supervisor*][supervisorScope]: + * + * ``` + * supervisorScope { + * val producers = List(10) { + * produce { + * delay(10.milliseconds * it) + * throw IllegalStateException("$it is tired of all this") + * } + * } + * producers.forEach { + * println(runCatching { it.receive() }) + * } + * } + * ``` + * + * Every coroutine here will run to completion and will fail with its own error. + * + * For non-lexically-scoped [CoroutineScope] instances, + * use [SupervisorJob] instead of [Job] when constructing the [CoroutineScope]. + * + * If only *some* coroutines need to individually have their failures invisible to others + * in a non-lexically-scoped [CoroutineScope], + * the correct approach from the point of view of structured concurrency is this: + * + * ``` + * val childSupervisorScope = CoroutineScope( + * scope.coroutineContext + SupervisorJob(scope.coroutineContext.job) + * ) + * childSupervisorScope.produce { + * // failures in this coroutine will not affect other children + * } + * ``` + * + * For a lexically scoped [CoroutineScope], using a [supervisorScope] at the end of the outer scope may help: + * + * ``` + * coroutineScope { + * launch { + * // failures in this coroutine will affect everyone + * } + * supervisorScope { + * val channel = produce { + * // failures in this coroutine + * // are only available through `channel` + * } + * } + * // this line will only be reached when the `produce` coroutine complete + * } + * // this line will be reached when both `launch` and `produce` complete + * ``` + * + * All of these approaches preserve the ability of a parent to cancel the children and to wait for their completion. + * + * ### Avoiding both cancelling and being cancelled + * + * Sometimes, coroutines to be spawned are just completely unrelated to the [CoroutineScope] used as the receiver, + * and no structured concurrency mechanisms are needed. + * + * In that case, [GlobalScope] is the semantically clearer way of expressing opting out of structured concurrency: + * + * ``` + * GlobalScope.produce { + * // this is explicitly a rogue computation + * } + * ``` + * + * The reason why [GlobalScope] is marked as [delicate][DelicateCoroutinesApi] is exactly that the coroutines + * created in it are outside structured concurrency. + */ +@Deprecated( + "Passing a Job to coroutine builders breaks structured concurrency, leading to hard-to-diagnose errors. " + + "This pattern should be avoided. " + + "This overload will be deprecated with an error in the future.", + level = DeprecationLevel.WARNING) +public fun CoroutineScope.produce( + context: Job, + capacity: Int = Channel.RENDEZVOUS, + @BuilderInference block: suspend ProducerScope.() -> Unit +): ReceiveChannel = + produce(context as CoroutineContext, capacity, block) diff --git a/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt b/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt index 6fd11ab107..e760b796d4 100644 --- a/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt +++ b/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt @@ -38,6 +38,26 @@ import kotlin.jvm.JvmName * the specified dispatcher while the current thread is blocked. If the specified dispatcher is an event loop of another `runBlocking`, * then this invocation uses the outer event loop. * + * If the [context] contains a [Job], + * that job becomes the parent of the [CoroutineScope] passed as the receiver to the [block]. + * This can be used to establish a structured concurrency relationship across the boundary of a non-`suspend` function: + * + * ``` + * val currentCoroutine = ThreadLocal() + * + * interface BlockingInterface { + * fun performSomeBlockingTask() + * } + * + * object AsyncImplOfBlockingInterface: BlockingInterface { + * override fun performSomeBlockingTask() { + * runBlocking(currentCoroutine.get() ?: EmptyCoroutineContext) { + * // do some async task + * } + * } + * } + * ``` + * * If this blocked thread is interrupted (see `Thread.interrupt`), then the coroutine job is cancelled and * this `runBlocking` invocation throws `InterruptedException`. * diff --git a/kotlinx-coroutines-core/js/src/Guidance.kt b/kotlinx-coroutines-core/js/src/Guidance.kt new file mode 100644 index 0000000000..5df54703ec --- /dev/null +++ b/kotlinx-coroutines-core/js/src/Guidance.kt @@ -0,0 +1,24 @@ +package kotlinx.coroutines + +import kotlin.coroutines.CoroutineContext +import kotlin.js.Promise + +/** + * Deprecated version of [promise] that accepts a [Job]. + * + * See the documentation for the non-deprecated [promise] function to learn about the functionality of this function. + * + * See the documentation for the deprecated [async] overload accepting a [Job] for an explanation of the reason + * this pattern is deprecated and the list of possible alternatives. + */ +@Deprecated( + "Passing a Job to coroutine builders breaks structured concurrency, leading to hard-to-diagnose errors. " + + "This pattern should be avoided. " + + "This overload will be deprecated with an error in the future.", + level = DeprecationLevel.WARNING) +public fun CoroutineScope.promise( + context: Job, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> T +): Promise = + promise(context as CoroutineContext, start, block) diff --git a/kotlinx-coroutines-core/jvm/src/GuidanceJvm.kt b/kotlinx-coroutines-core/jvm/src/GuidanceJvm.kt new file mode 100644 index 0000000000..1b5a9c8ff4 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/src/GuidanceJvm.kt @@ -0,0 +1,23 @@ +package kotlinx.coroutines + +import kotlin.coroutines.CoroutineContext + +/** + * Deprecated version of [runInterruptible] that accepts a [Job]. + * + * The purpose of the [runInterruptible] function is to interrupt the code executing in [block] + * when the caller gets cancelled, but passing a [Job] in [context] breaks the structured concurrency relationship + * between the code being invoked and the code running in [block]. + * + * See the [withContext] documentation for a description of how to ensure the [block] gets cancelled when a non-caller + * [Job] gets cancelled. + */ +@Deprecated( + "Passing a Job to `runInterruptible` prevents it from being cancelled when the caller gets cancelled. " + + "This pattern should be avoided. " + + "This overload will be deprecated with an error in the future.", + level = DeprecationLevel.WARNING) +public suspend fun runInterruptible( + context: Job, + block: () -> T +): T = runInterruptible(context as CoroutineContext, block) diff --git a/kotlinx-coroutines-core/jvm/src/future/Future.kt b/kotlinx-coroutines-core/jvm/src/future/Future.kt index 37620bbbdb..5c2f3d38ff 100644 --- a/kotlinx-coroutines-core/jvm/src/future/Future.kt +++ b/kotlinx-coroutines-core/jvm/src/future/Future.kt @@ -41,6 +41,25 @@ public fun CoroutineScope.future( return future } +/** + * Deprecated version of [future] that accepts a [Job]. + * + * See the documentation for the non-deprecated [future] function to learn about the functionality of this function. + * + * See the documentation for the deprecated [async] overload accepting a [Job] for an explanation of the reason + * this pattern is deprecated and the list of possible alternatives. + */ +@Deprecated( + "Passing a Job to coroutine builders breaks structured concurrency, leading to hard-to-diagnose errors. " + + "This pattern should be avoided. " + + "This overload will be deprecated with an error in the future.", + level = DeprecationLevel.WARNING) +public fun CoroutineScope.future( + context: Job, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> T +): CompletableFuture = future(context as CoroutineContext, start, block) + private class CompletableFutureCoroutine( context: CoroutineContext, private val future: CompletableFuture diff --git a/kotlinx-coroutines-core/wasmJs/src/Guidance.kt b/kotlinx-coroutines-core/wasmJs/src/Guidance.kt new file mode 100644 index 0000000000..b601f5725a --- /dev/null +++ b/kotlinx-coroutines-core/wasmJs/src/Guidance.kt @@ -0,0 +1,24 @@ +package kotlinx.coroutines + +import kotlin.coroutines.CoroutineContext +import kotlin.js.Promise + +/** + * Deprecated version of [promise] that accepts a [Job]. + * + * See the documentation for the non-deprecated [promise] function to learn about the functionality of this function. + * + * See the documentation for the deprecated [async] overload accepting a [Job] for an explanation of the reason + * this pattern is deprecated and the list of possible alternatives. + */ +@Deprecated( + "Passing a Job to coroutine builders breaks structured concurrency, leading to hard-to-diagnose errors. " + + "This pattern should be avoided. " + + "This overload will be deprecated with an error in the future.", + level = DeprecationLevel.WARNING) +public fun CoroutineScope.promise( + context: Job, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> T +): Promise = + promise(context as CoroutineContext, start, block) diff --git a/reactive/kotlinx-coroutines-reactor/src/Convert.kt b/reactive/kotlinx-coroutines-reactor/src/Convert.kt index 97a2649f1b..5bbc109ff6 100644 --- a/reactive/kotlinx-coroutines-reactor/src/Convert.kt +++ b/reactive/kotlinx-coroutines-reactor/src/Convert.kt @@ -16,6 +16,7 @@ import kotlin.coroutines.* * in the future to account for the concept of structured concurrency. * * @param context -- the coroutine context from which the resulting mono is going to be signalled + * @throws IllegalArgumentException if the [context] contains a [Job] instance. */ public fun Job.asMono(context: CoroutineContext): Mono = mono(context) { this@asMono.join() } /** @@ -29,6 +30,7 @@ public fun Job.asMono(context: CoroutineContext): Mono = mono(context) { t * in the future to account for the concept of structured concurrency. * * @param context -- the coroutine context from which the resulting mono is going to be signalled + * @throws IllegalArgumentException if the [context] contains a [Job] instance. */ public fun Deferred.asMono(context: CoroutineContext): Mono = mono(context) { this@asMono.await() } diff --git a/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt b/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt index 980fb17693..6269e432b3 100644 --- a/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt +++ b/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt @@ -21,6 +21,7 @@ import kotlin.coroutines.* * in the future to account for the concept of structured concurrency. * * @param context -- the coroutine context from which the resulting completable is going to be signalled + * @throws IllegalArgumentException if the [context] contains a [Job] instance. */ public fun Job.asCompletable(context: CoroutineContext): Completable = rxCompletable(context) { this@asCompletable.join() @@ -37,6 +38,7 @@ public fun Job.asCompletable(context: CoroutineContext): Completable = rxComplet * in the future to account for the concept of structured concurrency. * * @param context -- the coroutine context from which the resulting maybe is going to be signalled + * @throws IllegalArgumentException if the [context] contains a [Job] instance. */ public fun Deferred.asMaybe(context: CoroutineContext): Maybe = rxMaybe(context) { this@asMaybe.await() @@ -53,6 +55,7 @@ public fun Deferred.asMaybe(context: CoroutineContext): Maybe = rxMay * in the future to account for the concept of structured concurrency. * * @param context -- the coroutine context from which the resulting single is going to be signalled + * @throws IllegalArgumentException if the [context] contains a [Job] instance. */ public fun Deferred.asSingle(context: CoroutineContext): Single = rxSingle(context) { this@asSingle.await() diff --git a/reactive/kotlinx-coroutines-rx3/src/RxConvert.kt b/reactive/kotlinx-coroutines-rx3/src/RxConvert.kt index a3731f85ab..dab6d38ca2 100644 --- a/reactive/kotlinx-coroutines-rx3/src/RxConvert.kt +++ b/reactive/kotlinx-coroutines-rx3/src/RxConvert.kt @@ -21,6 +21,7 @@ import kotlin.coroutines.* * in the future to account for the concept of structured concurrency. * * @param context -- the coroutine context from which the resulting completable is going to be signalled + * @throws IllegalArgumentException if the [context] contains a [Job] instance. */ public fun Job.asCompletable(context: CoroutineContext): Completable = rxCompletable(context) { this@asCompletable.join() @@ -37,6 +38,7 @@ public fun Job.asCompletable(context: CoroutineContext): Completable = rxComplet * in the future to account for the concept of structured concurrency. * * @param context -- the coroutine context from which the resulting maybe is going to be signalled + * @throws IllegalArgumentException if the [context] contains a [Job] instance. */ public fun Deferred.asMaybe(context: CoroutineContext): Maybe = rxMaybe(context) { this@asMaybe.await() @@ -53,6 +55,7 @@ public fun Deferred.asMaybe(context: CoroutineContext): Maybe = * in the future to account for the concept of structured concurrency. * * @param context -- the coroutine context from which the resulting single is going to be signalled + * @throws IllegalArgumentException if the [context] contains a [Job] instance. */ public fun Deferred.asSingle(context: CoroutineContext): Single = rxSingle(context) { this@asSingle.await() From 6b72b9bb40e2c98015419145131dddc2ae910edb Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Tue, 13 May 2025 15:27:38 +0200 Subject: [PATCH 2/3] API dump --- .../api/kotlinx-coroutines-guava.api | 2 ++ .../api/kotlinx-coroutines-core.api | 12 ++++++++++++ .../api/kotlinx-coroutines-core.klib.api | 9 +++++++++ 3 files changed, 23 insertions(+) diff --git a/integration/kotlinx-coroutines-guava/api/kotlinx-coroutines-guava.api b/integration/kotlinx-coroutines-guava/api/kotlinx-coroutines-guava.api index 26bc229e9d..f3a707ec47 100644 --- a/integration/kotlinx-coroutines-guava/api/kotlinx-coroutines-guava.api +++ b/integration/kotlinx-coroutines-guava/api/kotlinx-coroutines-guava.api @@ -3,6 +3,8 @@ public final class kotlinx/coroutines/guava/ListenableFutureKt { public static final fun asListenableFuture (Lkotlinx/coroutines/Deferred;)Lcom/google/common/util/concurrent/ListenableFuture; public static final fun await (Lcom/google/common/util/concurrent/ListenableFuture;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun future (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Lcom/google/common/util/concurrent/ListenableFuture; + public static final fun future (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/Job;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Lcom/google/common/util/concurrent/ListenableFuture; public static synthetic fun future$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/google/common/util/concurrent/ListenableFuture; + public static synthetic fun future$default (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/Job;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/google/common/util/concurrent/ListenableFuture; } diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index 9790ed05ab..61727ad090 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -381,11 +381,19 @@ public final class kotlinx/coroutines/GlobalScope : kotlinx/coroutines/Coroutine public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; } +public final class kotlinx/coroutines/GuidanceJvmKt { + public static final fun runInterruptible (Lkotlinx/coroutines/Job;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public final class kotlinx/coroutines/GuidanceKt { public static final fun async (Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Deferred; + public static final fun async (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/Job;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Deferred; public static synthetic fun async$default (Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Deferred; + public static synthetic fun async$default (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/Job;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Deferred; public static final fun launch (Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; + public static final fun launch (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/Job;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; public static synthetic fun launch$default (Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job; + public static synthetic fun launch$default (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/Job;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job; } public abstract interface annotation class kotlinx/coroutines/InternalCoroutinesApi : java/lang/annotation/Annotation { @@ -796,6 +804,8 @@ public final class kotlinx/coroutines/channels/ChannelsKt { public static final synthetic fun minWith (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Comparator;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun none (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun onReceiveOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/selects/SelectClause1; + public static final fun produce (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/Job;ILkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/channels/ReceiveChannel; + public static synthetic fun produce$default (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/Job;ILkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/channels/ReceiveChannel; public static final synthetic fun receiveOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun requireNoNulls (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/channels/ReceiveChannel; public static final synthetic fun sendBlocking (Lkotlinx/coroutines/channels/SendChannel;Ljava/lang/Object;)V @@ -1265,7 +1275,9 @@ public final class kotlinx/coroutines/future/FutureKt { public static final fun asDeferred (Ljava/util/concurrent/CompletionStage;)Lkotlinx/coroutines/Deferred; public static final fun await (Ljava/util/concurrent/CompletionStage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun future (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Ljava/util/concurrent/CompletableFuture; + public static final fun future (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/Job;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Ljava/util/concurrent/CompletableFuture; public static synthetic fun future$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/util/concurrent/CompletableFuture; + public static synthetic fun future$default (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/Job;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/util/concurrent/CompletableFuture; } public final class kotlinx/coroutines/intrinsics/CancellableKt { diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.klib.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.klib.api index effb60f649..8d23a3a983 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.klib.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.klib.api @@ -769,6 +769,7 @@ final fun (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines/cancel(kotlin.c final fun (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines/cancel(kotlin/String, kotlin/Throwable? = ...) // kotlinx.coroutines/cancel|cancel@kotlinx.coroutines.CoroutineScope(kotlin.String;kotlin.Throwable?){}[0] final fun (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines/ensureActive() // kotlinx.coroutines/ensureActive|ensureActive@kotlinx.coroutines.CoroutineScope(){}[0] final fun (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines/launch(kotlin.coroutines/CoroutineContext = ..., kotlinx.coroutines/CoroutineStart = ..., kotlin.coroutines/SuspendFunction1): kotlinx.coroutines/Job // kotlinx.coroutines/launch|launch@kotlinx.coroutines.CoroutineScope(kotlin.coroutines.CoroutineContext;kotlinx.coroutines.CoroutineStart;kotlin.coroutines.SuspendFunction1){}[0] +final fun (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines/launch(kotlinx.coroutines/Job, kotlinx.coroutines/CoroutineStart = ..., kotlin.coroutines/SuspendFunction1): kotlinx.coroutines/Job // kotlinx.coroutines/launch|launch@kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Job;kotlinx.coroutines.CoroutineStart;kotlin.coroutines.SuspendFunction1){}[0] final fun (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines/newCoroutineContext(kotlin.coroutines/CoroutineContext): kotlin.coroutines/CoroutineContext // kotlinx.coroutines/newCoroutineContext|newCoroutineContext@kotlinx.coroutines.CoroutineScope(kotlin.coroutines.CoroutineContext){}[0] final fun (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines/plus(kotlin.coroutines/CoroutineContext): kotlinx.coroutines/CoroutineScope // kotlinx.coroutines/plus|plus@kotlinx.coroutines.CoroutineScope(kotlin.coroutines.CoroutineContext){}[0] final fun (kotlinx.coroutines/Job).kotlinx.coroutines/cancel(kotlin/String, kotlin/Throwable? = ...) // kotlinx.coroutines/cancel|cancel@kotlinx.coroutines.Job(kotlin.String;kotlin.Throwable?){}[0] @@ -912,7 +913,9 @@ final fun <#A: kotlin/Any?> (kotlinx.coroutines/CompletableDeferred<#A>).kotlinx final fun <#A: kotlin/Any?> (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines.channels/broadcast(kotlin.coroutines/CoroutineContext = ..., kotlin/Int = ..., kotlinx.coroutines/CoroutineStart = ..., kotlin/Function1? = ..., kotlin.coroutines/SuspendFunction1, kotlin/Unit>): kotlinx.coroutines.channels/BroadcastChannel<#A> // kotlinx.coroutines.channels/broadcast|broadcast@kotlinx.coroutines.CoroutineScope(kotlin.coroutines.CoroutineContext;kotlin.Int;kotlinx.coroutines.CoroutineStart;kotlin.Function1?;kotlin.coroutines.SuspendFunction1,kotlin.Unit>){0§}[0] final fun <#A: kotlin/Any?> (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines.channels/produce(kotlin.coroutines/CoroutineContext = ..., kotlin/Int = ..., kotlin.coroutines/SuspendFunction1, kotlin/Unit>): kotlinx.coroutines.channels/ReceiveChannel<#A> // kotlinx.coroutines.channels/produce|produce@kotlinx.coroutines.CoroutineScope(kotlin.coroutines.CoroutineContext;kotlin.Int;kotlin.coroutines.SuspendFunction1,kotlin.Unit>){0§}[0] final fun <#A: kotlin/Any?> (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines.channels/produce(kotlin.coroutines/CoroutineContext = ..., kotlin/Int = ..., kotlinx.coroutines/CoroutineStart = ..., kotlin/Function1? = ..., kotlin.coroutines/SuspendFunction1, kotlin/Unit>): kotlinx.coroutines.channels/ReceiveChannel<#A> // kotlinx.coroutines.channels/produce|produce@kotlinx.coroutines.CoroutineScope(kotlin.coroutines.CoroutineContext;kotlin.Int;kotlinx.coroutines.CoroutineStart;kotlin.Function1?;kotlin.coroutines.SuspendFunction1,kotlin.Unit>){0§}[0] +final fun <#A: kotlin/Any?> (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines.channels/produce(kotlinx.coroutines/Job, kotlin/Int = ..., kotlin.coroutines/SuspendFunction1, kotlin/Unit>): kotlinx.coroutines.channels/ReceiveChannel<#A> // kotlinx.coroutines.channels/produce|produce@kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Job;kotlin.Int;kotlin.coroutines.SuspendFunction1,kotlin.Unit>){0§}[0] final fun <#A: kotlin/Any?> (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines/async(kotlin.coroutines/CoroutineContext = ..., kotlinx.coroutines/CoroutineStart = ..., kotlin.coroutines/SuspendFunction1): kotlinx.coroutines/Deferred<#A> // kotlinx.coroutines/async|async@kotlinx.coroutines.CoroutineScope(kotlin.coroutines.CoroutineContext;kotlinx.coroutines.CoroutineStart;kotlin.coroutines.SuspendFunction1){0§}[0] +final fun <#A: kotlin/Any?> (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines/async(kotlinx.coroutines/Job, kotlinx.coroutines/CoroutineStart = ..., kotlin.coroutines/SuspendFunction1): kotlinx.coroutines/Deferred<#A> // kotlinx.coroutines/async|async@kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Job;kotlinx.coroutines.CoroutineStart;kotlin.coroutines.SuspendFunction1){0§}[0] final fun <#A: kotlin/Any?> kotlinx.coroutines.channels/BroadcastChannel(kotlin/Int): kotlinx.coroutines.channels/BroadcastChannel<#A> // kotlinx.coroutines.channels/BroadcastChannel|BroadcastChannel(kotlin.Int){0§}[0] final fun <#A: kotlin/Any?> kotlinx.coroutines.channels/Channel(kotlin/Int = ...): kotlinx.coroutines.channels/Channel<#A> // kotlinx.coroutines.channels/Channel|Channel(kotlin.Int){0§}[0] final fun <#A: kotlin/Any?> kotlinx.coroutines.channels/Channel(kotlin/Int = ..., kotlinx.coroutines.channels/BufferOverflow = ..., kotlin/Function1<#A, kotlin/Unit>? = ...): kotlinx.coroutines.channels/Channel<#A> // kotlinx.coroutines.channels/Channel|Channel(kotlin.Int;kotlinx.coroutines.channels.BufferOverflow;kotlin.Function1<0:0,kotlin.Unit>?){0§}[0] @@ -1083,6 +1086,9 @@ final fun <#A: kotlin/Any?> (kotlin.js/Promise<#A>).kotlinx.coroutines/asDeferre // Targets: [js] final fun <#A: kotlin/Any?> (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines/promise(kotlin.coroutines/CoroutineContext = ..., kotlinx.coroutines/CoroutineStart = ..., kotlin.coroutines/SuspendFunction1): kotlin.js/Promise<#A> // kotlinx.coroutines/promise|promise@kotlinx.coroutines.CoroutineScope(kotlin.coroutines.CoroutineContext;kotlinx.coroutines.CoroutineStart;kotlin.coroutines.SuspendFunction1){0§}[0] +// Targets: [js] +final fun <#A: kotlin/Any?> (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines/promise(kotlinx.coroutines/Job, kotlinx.coroutines/CoroutineStart = ..., kotlin.coroutines/SuspendFunction1): kotlin.js/Promise<#A> // kotlinx.coroutines/promise|promise@kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Job;kotlinx.coroutines.CoroutineStart;kotlin.coroutines.SuspendFunction1){0§}[0] + // Targets: [js] final fun <#A: kotlin/Any?> (kotlinx.coroutines/Deferred<#A>).kotlinx.coroutines/asPromise(): kotlin.js/Promise<#A> // kotlinx.coroutines/asPromise|asPromise@kotlinx.coroutines.Deferred<0:0>(){0§}[0] @@ -1098,6 +1104,9 @@ final fun <#A: kotlin/Any?> (kotlin.js/Promise).kotlinx.corout // Targets: [wasmJs] final fun <#A: kotlin/Any?> (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines/promise(kotlin.coroutines/CoroutineContext = ..., kotlinx.coroutines/CoroutineStart = ..., kotlin.coroutines/SuspendFunction1): kotlin.js/Promise // kotlinx.coroutines/promise|promise@kotlinx.coroutines.CoroutineScope(kotlin.coroutines.CoroutineContext;kotlinx.coroutines.CoroutineStart;kotlin.coroutines.SuspendFunction1){0§}[0] +// Targets: [wasmJs] +final fun <#A: kotlin/Any?> (kotlinx.coroutines/CoroutineScope).kotlinx.coroutines/promise(kotlinx.coroutines/Job, kotlinx.coroutines/CoroutineStart = ..., kotlin.coroutines/SuspendFunction1): kotlin.js/Promise // kotlinx.coroutines/promise|promise@kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Job;kotlinx.coroutines.CoroutineStart;kotlin.coroutines.SuspendFunction1){0§}[0] + // Targets: [wasmJs] final fun <#A: kotlin/Any?> (kotlinx.coroutines/Deferred<#A>).kotlinx.coroutines/asPromise(): kotlin.js/Promise // kotlinx.coroutines/asPromise|asPromise@kotlinx.coroutines.Deferred<0:0>(){0§}[0] From 8bdba22baed7a2ce6bae321eae3a053eddef7865 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Mon, 19 May 2025 09:48:05 +0200 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Luca Kellermann --- kotlinx-coroutines-core/common/src/Guidance.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/Guidance.kt b/kotlinx-coroutines-core/common/src/Guidance.kt index bdbd20b8f5..77ddbde4ae 100644 --- a/kotlinx-coroutines-core/common/src/Guidance.kt +++ b/kotlinx-coroutines-core/common/src/Guidance.kt @@ -69,7 +69,7 @@ public fun launch( * ### Avoid cancelling other children * * Occasionally, the failure of one child does not mean the work of the other children is also unneeded. - * `launch(Job()) { failure() }` makes sure that the only effect of `failure()` is to make *this* `async` finish + * `launch(Job()) { failure() }` makes sure that the only effect of `failure()` is to make *this* `launch` finish * with an error, while the other coroutines continue executing. * * If *all* coroutines in a scope should fail independently, this suggests that the scope @@ -121,13 +121,13 @@ public fun launch( * // failures in this coroutine will affect everyone * } * supervisorScope { - * val deferred = launch( + * val job = launch( * CoroutineExceptionHandler { _, e -> * // some individual mechanism of processing exceptions * } * ) { * // failures in this coroutine - * // are only available through `deferred` + * // are only available through `job` * } * } * // this line will only be reached when `launch` completes