Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide resume and tryResume that pass the value to the callback #4090

Merged
8 changes: 5 additions & 3 deletions kotlinx-coroutines-core/api/kotlinx-coroutines-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ public abstract interface class kotlinx/coroutines/CancellableContinuation : kot
public abstract fun isCancelled ()Z
public abstract fun isCompleted ()Z
public abstract fun resume (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V
public abstract fun resume (Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
public abstract fun resumeUndispatched (Lkotlinx/coroutines/CoroutineDispatcher;Ljava/lang/Object;)V
public abstract fun resumeUndispatchedWithException (Lkotlinx/coroutines/CoroutineDispatcher;Ljava/lang/Throwable;)V
public abstract fun tryResume (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
public abstract fun tryResume (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public abstract fun tryResume (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
public abstract fun tryResumeWithException (Ljava/lang/Throwable;)Ljava/lang/Object;
}

Expand All @@ -54,7 +55,7 @@ public final class kotlinx/coroutines/CancellableContinuation$DefaultImpls {
public class kotlinx/coroutines/CancellableContinuationImpl : kotlin/coroutines/jvm/internal/CoroutineStackFrame, kotlinx/coroutines/CancellableContinuation, kotlinx/coroutines/Waiter {
public fun <init> (Lkotlin/coroutines/Continuation;I)V
public final fun callCancelHandler (Lkotlinx/coroutines/CancelHandler;Ljava/lang/Throwable;)V
public final fun callOnCancellation (Lkotlin/jvm/functions/Function1;Ljava/lang/Throwable;)V
public final fun callOnCancellation (Lkotlin/jvm/functions/Function2;Ljava/lang/Throwable;Ljava/lang/Object;)V
public fun cancel (Ljava/lang/Throwable;)Z
public fun completeResume (Ljava/lang/Object;)V
public fun getCallerFrame ()Lkotlin/coroutines/jvm/internal/CoroutineStackFrame;
Expand All @@ -70,12 +71,13 @@ public class kotlinx/coroutines/CancellableContinuationImpl : kotlin/coroutines/
public fun isCompleted ()Z
protected fun nameString ()Ljava/lang/String;
public fun resume (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)V
public fun resume (Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
public fun resumeUndispatched (Lkotlinx/coroutines/CoroutineDispatcher;Ljava/lang/Object;)V
public fun resumeUndispatchedWithException (Lkotlinx/coroutines/CoroutineDispatcher;Ljava/lang/Throwable;)V
public fun resumeWith (Ljava/lang/Object;)V
public fun toString ()Ljava/lang/String;
public fun tryResume (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
public fun tryResume (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public fun tryResume (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
public fun tryResumeWithException (Ljava/lang/Throwable;)Ljava/lang/Object;
}

Expand Down
8 changes: 5 additions & 3 deletions kotlinx-coroutines-core/api/kotlinx-coroutines-core.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,14 @@ abstract interface <#A: in kotlin/Any?> kotlinx.coroutines.channels/SendChannel
abstract interface <#A: in kotlin/Any?> kotlinx.coroutines/CancellableContinuation : kotlin.coroutines/Continuation<#A> { // kotlinx.coroutines/CancellableContinuation|null[0]
abstract fun (kotlinx.coroutines/CoroutineDispatcher).resumeUndispatched(#A) // kotlinx.coroutines/CancellableContinuation.resumeUndispatched|resumeUndispatched@kotlinx.coroutines.CoroutineDispatcher(1:0){}[0]
abstract fun (kotlinx.coroutines/CoroutineDispatcher).resumeUndispatchedWithException(kotlin/Throwable) // kotlinx.coroutines/CancellableContinuation.resumeUndispatchedWithException|resumeUndispatchedWithException@kotlinx.coroutines.CoroutineDispatcher(kotlin.Throwable){}[0]
abstract fun <#A1: #A> resume(#A1, kotlin/Function2<kotlin/Throwable, #A1, kotlin/Unit>?) // kotlinx.coroutines/CancellableContinuation.resume|resume(0:0;kotlin.Function2<kotlin.Throwable,0:0,kotlin.Unit>?){0§<1:0>}[0]
abstract fun <#A1: #A> tryResume(#A1, kotlin/Any?, kotlin/Function2<kotlin/Throwable, #A1, kotlin/Unit>?): kotlin/Any? // kotlinx.coroutines/CancellableContinuation.tryResume|tryResume(0:0;kotlin.Any?;kotlin.Function2<kotlin.Throwable,0:0,kotlin.Unit>?){0§<1:0>}[0]
abstract fun cancel(kotlin/Throwable? = ...): kotlin/Boolean // kotlinx.coroutines/CancellableContinuation.cancel|cancel(kotlin.Throwable?){}[0]
abstract fun completeResume(kotlin/Any) // kotlinx.coroutines/CancellableContinuation.completeResume|completeResume(kotlin.Any){}[0]
abstract fun initCancellability() // kotlinx.coroutines/CancellableContinuation.initCancellability|initCancellability(){}[0]
abstract fun invokeOnCancellation(kotlin/Function1<kotlin/Throwable?, kotlin/Unit>) // kotlinx.coroutines/CancellableContinuation.invokeOnCancellation|invokeOnCancellation(kotlin.Function1<kotlin.Throwable?,kotlin.Unit>){}[0]
abstract fun resume(#A, kotlin/Function1<kotlin/Throwable, kotlin/Unit>?) // kotlinx.coroutines/CancellableContinuation.resume|resume(1:0;kotlin.Function1<kotlin.Throwable,kotlin.Unit>?){}[0]
abstract fun tryResume(#A, kotlin/Any? = ...): kotlin/Any? // kotlinx.coroutines/CancellableContinuation.tryResume|tryResume(1:0;kotlin.Any?){}[0]
abstract fun tryResume(#A, kotlin/Any?, kotlin/Function1<kotlin/Throwable, kotlin/Unit>?): kotlin/Any? // kotlinx.coroutines/CancellableContinuation.tryResume|tryResume(1:0;kotlin.Any?;kotlin.Function1<kotlin.Throwable,kotlin.Unit>?){}[0]
abstract fun tryResumeWithException(kotlin/Throwable): kotlin/Any? // kotlinx.coroutines/CancellableContinuation.tryResumeWithException|tryResumeWithException(kotlin.Throwable){}[0]
abstract val isActive // kotlinx.coroutines/CancellableContinuation.isActive|{}isActive[0]
abstract fun <get-isActive>(): kotlin/Boolean // kotlinx.coroutines/CancellableContinuation.isActive.<get-isActive>|<get-isActive>(){}[0]
Expand Down Expand Up @@ -773,11 +774,13 @@ open annotation class kotlinx.coroutines/ObsoleteCoroutinesApi : kotlin/Annotati
}
open class <#A: in kotlin/Any?> kotlinx.coroutines/CancellableContinuationImpl : kotlinx.coroutines.internal/CoroutineStackFrame, kotlinx.coroutines/CancellableContinuation<#A>, kotlinx.coroutines/DispatchedTask<#A>, kotlinx.coroutines/Waiter { // kotlinx.coroutines/CancellableContinuationImpl|null[0]
constructor <init>(kotlin.coroutines/Continuation<#A>, kotlin/Int) // kotlinx.coroutines/CancellableContinuationImpl.<init>|<init>(kotlin.coroutines.Continuation<1:0>;kotlin.Int){}[0]
final fun <#A1: kotlin/Any?> callOnCancellation(kotlin/Function2<kotlin/Throwable, #A1, kotlin/Unit>, kotlin/Throwable, #A1) // kotlinx.coroutines/CancellableContinuationImpl.callOnCancellation|callOnCancellation(kotlin.Function2<kotlin.Throwable,0:0,kotlin.Unit>;kotlin.Throwable;0:0){0§<kotlin.Any?>}[0]
final fun callCancelHandler(kotlinx.coroutines/CancelHandler, kotlin/Throwable?) // kotlinx.coroutines/CancellableContinuationImpl.callCancelHandler|callCancelHandler(kotlinx.coroutines.CancelHandler;kotlin.Throwable?){}[0]
final fun callOnCancellation(kotlin/Function1<kotlin/Throwable, kotlin/Unit>, kotlin/Throwable) // kotlinx.coroutines/CancellableContinuationImpl.callOnCancellation|callOnCancellation(kotlin.Function1<kotlin.Throwable,kotlin.Unit>;kotlin.Throwable){}[0]
final fun getResult(): kotlin/Any? // kotlinx.coroutines/CancellableContinuationImpl.getResult|getResult(){}[0]
open fun (kotlinx.coroutines/CoroutineDispatcher).resumeUndispatched(#A) // kotlinx.coroutines/CancellableContinuationImpl.resumeUndispatched|resumeUndispatched@kotlinx.coroutines.CoroutineDispatcher(1:0){}[0]
open fun (kotlinx.coroutines/CoroutineDispatcher).resumeUndispatchedWithException(kotlin/Throwable) // kotlinx.coroutines/CancellableContinuationImpl.resumeUndispatchedWithException|resumeUndispatchedWithException@kotlinx.coroutines.CoroutineDispatcher(kotlin.Throwable){}[0]
open fun <#A1: #A> resume(#A1, kotlin/Function2<kotlin/Throwable, #A1, kotlin/Unit>?) // kotlinx.coroutines/CancellableContinuationImpl.resume|resume(0:0;kotlin.Function2<kotlin.Throwable,0:0,kotlin.Unit>?){0§<1:0>}[0]
open fun <#A1: #A> tryResume(#A1, kotlin/Any?, kotlin/Function2<kotlin/Throwable, #A1, kotlin/Unit>?): kotlin/Any? // kotlinx.coroutines/CancellableContinuationImpl.tryResume|tryResume(0:0;kotlin.Any?;kotlin.Function2<kotlin.Throwable,0:0,kotlin.Unit>?){0§<1:0>}[0]
open fun cancel(kotlin/Throwable?): kotlin/Boolean // kotlinx.coroutines/CancellableContinuationImpl.cancel|cancel(kotlin.Throwable?){}[0]
open fun completeResume(kotlin/Any) // kotlinx.coroutines/CancellableContinuationImpl.completeResume|completeResume(kotlin.Any){}[0]
open fun getContinuationCancellationCause(kotlinx.coroutines/Job): kotlin/Throwable // kotlinx.coroutines/CancellableContinuationImpl.getContinuationCancellationCause|getContinuationCancellationCause(kotlinx.coroutines.Job){}[0]
Expand All @@ -790,7 +793,6 @@ open class <#A: in kotlin/Any?> kotlinx.coroutines/CancellableContinuationImpl :
open fun resumeWith(kotlin/Result<#A>) // kotlinx.coroutines/CancellableContinuationImpl.resumeWith|resumeWith(kotlin.Result<1:0>){}[0]
open fun toString(): kotlin/String // kotlinx.coroutines/CancellableContinuationImpl.toString|toString(){}[0]
open fun tryResume(#A, kotlin/Any?): kotlin/Any? // kotlinx.coroutines/CancellableContinuationImpl.tryResume|tryResume(1:0;kotlin.Any?){}[0]
open fun tryResume(#A, kotlin/Any?, kotlin/Function1<kotlin/Throwable, kotlin/Unit>?): kotlin/Any? // kotlinx.coroutines/CancellableContinuationImpl.tryResume|tryResume(1:0;kotlin.Any?;kotlin.Function1<kotlin.Throwable,kotlin.Unit>?){}[0]
open fun tryResumeWithException(kotlin/Throwable): kotlin/Any? // kotlinx.coroutines/CancellableContinuationImpl.tryResumeWithException|tryResumeWithException(kotlin.Throwable){}[0]
open val callerFrame // kotlinx.coroutines/CancellableContinuationImpl.callerFrame|{}callerFrame[0]
open fun <get-callerFrame>(): kotlinx.coroutines.internal/CoroutineStackFrame? // kotlinx.coroutines/CancellableContinuationImpl.callerFrame.<get-callerFrame>|<get-callerFrame>(){}[0]
Expand Down
66 changes: 48 additions & 18 deletions kotlinx-coroutines-core/common/src/CancellableContinuation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,21 @@ public interface CancellableContinuation<in T> : Continuation<T> {
public fun tryResume(value: T, idempotent: Any? = null): Any?

/**
* Same as [tryResume] but with [onCancellation] handler that called if and only if the value is not
* delivered to the caller because of the dispatch in the process, so that atomicity delivery
* guaranteed can be provided by having a cancellation fallback.
* Same as [tryResume] but with an [onCancellation] handler that is called if and only if the value is not
* delivered to the caller because of the dispatch in the process.
*
* The purpose of this function is to enable atomic delivery guarantees: either resumption succeeded, passing
* the responsibility for [value] to the continuation, or the [onCancellation] block will be invoked,
* allowing one to free the resources in [value].
*
* Implementation note: current implementation always returns RESUME_TOKEN or `null`
*
* @suppress **This is unstable API and it is subject to change.**
*/
@InternalCoroutinesApi
public fun tryResume(value: T, idempotent: Any?, onCancellation: ((cause: Throwable) -> Unit)?): Any?
public fun <R: T> tryResume(
value: R, idempotent: Any?, onCancellation: ((cause: Throwable, value: R, context: CoroutineContext) -> Unit)?
): Any?

/**
* Tries to resume this continuation with the specified [exception] and returns a non-null object token if successful,
Expand Down Expand Up @@ -168,36 +173,59 @@ public interface CancellableContinuation<in T> : Continuation<T> {
@ExperimentalCoroutinesApi
public fun CoroutineDispatcher.resumeUndispatchedWithException(exception: Throwable)

/** @suppress */
@Deprecated(
"Use the overload that also accepts the `value` and the coroutine context in lambda",
level = DeprecationLevel.WARNING
dkhalanskyjb marked this conversation as resolved.
Show resolved Hide resolved
) // warning since 1.9.0, was experimental
public fun resume(value: T, onCancellation: ((cause: Throwable) -> Unit)?)

/**
* Resumes this continuation with the specified `value` and calls the specified `onCancellation`
* handler when either resumed too late (when continuation was already cancelled) or, although resumed
* successfully (before cancellation), the coroutine's job was cancelled before it had a
* chance to run in its dispatcher, so that the suspended function threw an exception
* instead of returning this value.
* Resumes this continuation with the specified [value], calling the specified [onCancellation] if and only if
* the [value] was not successfully used to resume the continuation.
*
* The [value] can be rejected in two cases (in both of which [onCancellation] will be called):
* - Cancellation happened before the handler was resumed;
* - The continuation was resumed successfully (before cancellation), but the coroutine's job was cancelled before
* it had a chance to run in its dispatcher, and so the suspended function threw an exception instead of returning
* this value.
*
* The installed [onCancellation] handler should not throw any exceptions.
* If it does, they will get caught, wrapped into a [CompletionHandlerException] and
* If it does, they will get caught, wrapped into a [CompletionHandlerException], and
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is effectively an internal class with no documentation. Probably backticked version will do

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* processed as an uncaught exception in the context of the current coroutine
* (see [CoroutineExceptionHandler]).
*
* This function shall be used when resuming with a resource that must be closed by
* code that called the corresponding suspending function, for example:
* With this version of [resume], it's possible to pass resources that can not simply be left for the garbage
* collector (like file handles, sockets, etc.) and need to be closed explicitly:
*
* ```
* continuation.resume(resource) {
* resource.close()
* continuation.resume(resourceToResumeWith) { cause, resourceToClose, context ->
dkhalanskyjb marked this conversation as resolved.
Show resolved Hide resolved
* resourceToClose.close()
* }
* ```
*
* [onCancellation] accepts three arguments:
*
* - `cause: Throwable` is the exception with which the continuation was cancelled.
* - `value` is exactly the same as the [value] passed to [resume] itself.
* In the example above, `resourceToResumeWith` is exactly the same as `resourceToClose`; in particular,
* one could call `resourceToResumeWith.close()` in the lambda for the same effect.
* The reason to reference `resourceToClose` anyway is to avoid a memory allocation due to the lambda
* capturing the `resourceToResumeWith` reference.
* - `context` is the [context] of this continuation.
* Like with `value`, the reason this is available as a lambda parameter, even though it is always possible to
* call [context] from the lambda instead, is to allow lambdas to capture less of their environment.
*
* A more complete example and further details are given in
* the documentation for the [suspendCancellableCoroutine] function.
*
* **Note**: The [onCancellation] handler must be fast, non-blocking, and thread-safe.
* It can be invoked concurrently with the surrounding code.
* There is no guarantee on the execution context of its invocation.
*/
@ExperimentalCoroutinesApi // since 1.2.0
public fun resume(value: T, onCancellation: ((cause: Throwable) -> Unit)?)
public fun <R: T> resume(
value: R, onCancellation: ((cause: Throwable, value: R, context: CoroutineContext) -> Unit)?
)
}

/**
Expand Down Expand Up @@ -293,8 +321,10 @@ internal fun <T> CancellableContinuation<T>.invokeOnCancellation(handler: Cancel
* override fun onCompleted(resource: T) {
* // Resume coroutine with a value provided by the callback and ensure the resource is closed in case
* // when the coroutine is cancelled before the caller gets a reference to the resource.
* continuation.resume(resource) {
* resource.close() // Close the resource on cancellation
* continuation.resume(resource) { cause, resourceToClose, context ->
* resourceToClose.close() // Close the resource on cancellation
* // If we used `resource` instead of `resourceToClose`, this lambda would need to allocate a closure,
* // but with `resourceToClose`, the lambda does not capture any of its environment.
* }
* }
* // ...
Expand Down
Loading