1 /* <lambda>null2 * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 */ 4 5 package kotlinx.coroutines 6 7 import kotlinx.atomicfu.* 8 import kotlinx.coroutines.internal.* 9 import kotlin.coroutines.* 10 import kotlin.coroutines.intrinsics.* 11 import kotlin.jvm.* 12 13 private const val UNDECIDED = 0 14 private const val SUSPENDED = 1 15 private const val RESUMED = 2 16 17 private const val DECISION_SHIFT = 29 18 private const val INDEX_MASK = (1 shl DECISION_SHIFT) - 1 19 private const val NO_INDEX = INDEX_MASK 20 21 private inline val Int.decision get() = this shr DECISION_SHIFT 22 private inline val Int.index get() = this and INDEX_MASK 23 @Suppress("NOTHING_TO_INLINE") 24 private inline fun decisionAndIndex(decision: Int, index: Int) = (decision shl DECISION_SHIFT) + index 25 26 @JvmField 27 internal val RESUME_TOKEN = Symbol("RESUME_TOKEN") 28 29 /** 30 * @suppress **This is unstable API and it is subject to change.** 31 */ 32 @PublishedApi 33 internal open class CancellableContinuationImpl<in T>( 34 final override val delegate: Continuation<T>, 35 resumeMode: Int 36 ) : DispatchedTask<T>(resumeMode), CancellableContinuation<T>, CoroutineStackFrame, Waiter { 37 init { 38 assert { resumeMode != MODE_UNINITIALIZED } // invalid mode for CancellableContinuationImpl 39 } 40 41 public override val context: CoroutineContext = delegate.context 42 43 /* 44 * Implementation notes 45 * 46 * CancellableContinuationImpl is a subset of Job with following limitations: 47 * 1) It can have only cancellation listener (no "on cancelling") 48 * 2) It always invokes cancellation listener if it's cancelled (no 'invokeImmediately') 49 * 3) It can have at most one cancellation listener 50 * 4) Its cancellation listeners cannot be deregistered 51 * As a consequence it has much simpler state machine, more lightweight machinery and 52 * less dependencies. 53 */ 54 55 /** decision state machine 56 57 +-----------+ trySuspend +-----------+ 58 | UNDECIDED | -------------> | SUSPENDED | 59 +-----------+ +-----------+ 60 | 61 | tryResume 62 V 63 +-----------+ 64 | RESUMED | 65 +-----------+ 66 67 Note: both tryResume and trySuspend can be invoked at most once, first invocation wins. 68 If the cancellation handler is specified via a [Segment] instance and the index in it 69 (so [Segment.onCancellation] should be called), the [_decisionAndIndex] field may store 70 this index additionally to the "decision" value. 71 */ 72 private val _decisionAndIndex = atomic(decisionAndIndex(UNDECIDED, NO_INDEX)) 73 74 /* 75 === Internal states === 76 name state class public state description 77 ------ ------------ ------------ ----------- 78 ACTIVE Active : Active active, no listeners 79 SINGLE_A CancelHandler : Active active, one cancellation listener 80 CANCELLED CancelledContinuation: Cancelled cancelled (final state) 81 COMPLETED any : Completed produced some result or threw an exception (final state) 82 */ 83 private val _state = atomic<Any?>(Active) 84 85 /* 86 * This field has a concurrent rendezvous in the following scenario: 87 * 88 * - installParentHandle publishes this instance on T1 89 * 90 * T1 writes: 91 * * handle = installed; right after the installation 92 * * Shortly after: if (isComplete) handle = NonDisposableHandle 93 * 94 * Any other T writes if the parent job is cancelled in detachChild: 95 * * handle = NonDisposableHandle 96 * 97 * We want to preserve a strict invariant on parentHandle transition, allowing only three of them: 98 * null -> anyHandle 99 * anyHandle -> NonDisposableHandle 100 * null -> NonDisposableHandle 101 * 102 * With a guarantee that after disposal the only state handle may end up in is NonDisposableHandle 103 */ 104 private val _parentHandle = atomic<DisposableHandle?>(null) 105 private val parentHandle: DisposableHandle? 106 get() = _parentHandle.value 107 108 internal val state: Any? get() = _state.value 109 110 public override val isActive: Boolean get() = state is NotCompleted 111 112 public override val isCompleted: Boolean get() = state !is NotCompleted 113 114 public override val isCancelled: Boolean get() = state is CancelledContinuation 115 116 // We cannot invoke `state.toString()` since it may cause a circular dependency 117 private val stateDebugRepresentation get() = when(state) { 118 is NotCompleted -> "Active" 119 is CancelledContinuation -> "Cancelled" 120 else -> "Completed" 121 } 122 123 public override fun initCancellability() { 124 /* 125 * Invariant: at the moment of invocation, `this` has not yet 126 * leaked to user code and no one is able to invoke `resume` or `cancel` 127 * on it yet. Also, this function is not invoked for reusable continuations. 128 */ 129 val handle = installParentHandle() 130 ?: return // fast path -- don't do anything without parent 131 // now check our state _after_ registering, could have completed while we were registering, 132 // but only if parent was cancelled. Parent could be in a "cancelling" state for a while, 133 // so we are helping it and cleaning the node ourselves 134 if (isCompleted) { 135 // Can be invoked concurrently in 'parentCancelled', no problems here 136 handle.dispose() 137 _parentHandle.value = NonDisposableHandle 138 } 139 } 140 141 private fun isReusable(): Boolean = resumeMode.isReusableMode && (delegate as DispatchedContinuation<*>).isReusable() 142 143 /** 144 * Resets cancellability state in order to [suspendCancellableCoroutineReusable] to work. 145 * Invariant: used only by [suspendCancellableCoroutineReusable] in [REUSABLE_CLAIMED] state. 146 */ 147 @JvmName("resetStateReusable") // Prettier stack traces 148 internal fun resetStateReusable(): Boolean { 149 assert { resumeMode == MODE_CANCELLABLE_REUSABLE } 150 assert { parentHandle !== NonDisposableHandle } 151 val state = _state.value 152 assert { state !is NotCompleted } 153 if (state is CompletedContinuation && state.idempotentResume != null) { 154 // Cannot reuse continuation that was resumed with idempotent marker 155 detachChild() 156 return false 157 } 158 _decisionAndIndex.value = decisionAndIndex(UNDECIDED, NO_INDEX) 159 _state.value = Active 160 return true 161 } 162 163 public override val callerFrame: CoroutineStackFrame? 164 get() = delegate as? CoroutineStackFrame 165 166 public override fun getStackTraceElement(): StackTraceElement? = null 167 168 override fun takeState(): Any? = state 169 170 // Note: takeState does not clear the state so we don't use takenState 171 // and we use the actual current state where in CAS-loop 172 override fun cancelCompletedResult(takenState: Any?, cause: Throwable): Unit = _state.loop { state -> 173 when (state) { 174 is NotCompleted -> error("Not completed") 175 is CompletedExceptionally -> return // already completed exception or cancelled, nothing to do 176 is CompletedContinuation -> { 177 check(!state.cancelled) { "Must be called at most once" } 178 val update = state.copy(cancelCause = cause) 179 if (_state.compareAndSet(state, update)) { 180 state.invokeHandlers(this, cause) 181 return // done 182 } 183 } 184 else -> { 185 // completed normally without marker class, promote to CompletedContinuation in case 186 // if invokeOnCancellation if called later 187 if (_state.compareAndSet(state, CompletedContinuation(state, cancelCause = cause))) { 188 return // done 189 } 190 } 191 } 192 } 193 194 /* 195 * Attempt to postpone cancellation for reusable cancellable continuation 196 */ 197 private fun cancelLater(cause: Throwable): Boolean { 198 // Ensure that we are postponing cancellation to the right reusable instance 199 if (!isReusable()) return false 200 val dispatched = delegate as DispatchedContinuation<*> 201 return dispatched.postponeCancellation(cause) 202 } 203 204 public override fun cancel(cause: Throwable?): Boolean { 205 _state.loop { state -> 206 if (state !is NotCompleted) return false // false if already complete or cancelling 207 // Active -- update to final state 208 val update = CancelledContinuation(this, cause, handled = state is CancelHandler || state is Segment<*>) 209 if (!_state.compareAndSet(state, update)) return@loop // retry on cas failure 210 // Invoke cancel handler if it was present 211 when (state) { 212 is CancelHandler -> callCancelHandler(state, cause) 213 is Segment<*> -> callSegmentOnCancellation(state, cause) 214 } 215 // Complete state update 216 detachChildIfNonResuable() 217 dispatchResume(resumeMode) // no need for additional cancellation checks 218 return true 219 } 220 } 221 222 internal fun parentCancelled(cause: Throwable) { 223 if (cancelLater(cause)) return 224 cancel(cause) 225 // Even if cancellation has failed, we should detach child to avoid potential leak 226 detachChildIfNonResuable() 227 } 228 229 private inline fun callCancelHandlerSafely(block: () -> Unit) { 230 try { 231 block() 232 } catch (ex: Throwable) { 233 // Handler should never fail, if it does -- it is an unhandled exception 234 handleCoroutineException( 235 context, 236 CompletionHandlerException("Exception in invokeOnCancellation handler for $this", ex) 237 ) 238 } 239 } 240 241 private fun callCancelHandler(handler: CompletionHandler, cause: Throwable?) = 242 /* 243 * :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension, 244 * because we play type tricks on Kotlin/JS and handler is not necessarily a function there 245 */ 246 callCancelHandlerSafely { handler.invokeIt(cause) } 247 248 fun callCancelHandler(handler: CancelHandler, cause: Throwable?) = 249 callCancelHandlerSafely { handler.invoke(cause) } 250 251 private fun callSegmentOnCancellation(segment: Segment<*>, cause: Throwable?) { 252 val index = _decisionAndIndex.value.index 253 check(index != NO_INDEX) { "The index for Segment.onCancellation(..) is broken" } 254 callCancelHandlerSafely { segment.onCancellation(index, cause, context) } 255 } 256 257 fun callOnCancellation(onCancellation: (cause: Throwable) -> Unit, cause: Throwable) { 258 try { 259 onCancellation.invoke(cause) 260 } catch (ex: Throwable) { 261 // Handler should never fail, if it does -- it is an unhandled exception 262 handleCoroutineException( 263 context, 264 CompletionHandlerException("Exception in resume onCancellation handler for $this", ex) 265 ) 266 } 267 } 268 269 /** 270 * It is used when parent is cancelled to get the cancellation cause for this continuation. 271 */ 272 open fun getContinuationCancellationCause(parent: Job): Throwable = 273 parent.getCancellationException() 274 275 private fun trySuspend(): Boolean { 276 _decisionAndIndex.loop { cur -> 277 when (cur.decision) { 278 UNDECIDED -> if (this._decisionAndIndex.compareAndSet(cur, decisionAndIndex(SUSPENDED, cur.index))) return true 279 RESUMED -> return false 280 else -> error("Already suspended") 281 } 282 } 283 } 284 285 private fun tryResume(): Boolean { 286 _decisionAndIndex.loop { cur -> 287 when (cur.decision) { 288 UNDECIDED -> if (this._decisionAndIndex.compareAndSet(cur, decisionAndIndex(RESUMED, cur.index))) return true 289 SUSPENDED -> return false 290 else -> error("Already resumed") 291 } 292 } 293 } 294 295 @PublishedApi 296 internal fun getResult(): Any? { 297 val isReusable = isReusable() 298 // trySuspend may fail either if 'block' has resumed/cancelled a continuation, 299 // or we got async cancellation from parent. 300 if (trySuspend()) { 301 /* 302 * Invariant: parentHandle is `null` *only* for reusable continuations. 303 * We were neither resumed nor cancelled, time to suspend. 304 * But first we have to install parent cancellation handle (if we didn't yet), 305 * so CC could be properly resumed on parent cancellation. 306 * 307 * This read has benign data-race with write of 'NonDisposableHandle' 308 * in 'detachChildIfNotReusable'. 309 */ 310 if (parentHandle == null) { 311 installParentHandle() 312 } 313 /* 314 * Release the continuation after installing the handle (if needed). 315 * If we were successful, then do nothing, it's ok to reuse the instance now. 316 * Otherwise, dispose the handle by ourselves. 317 */ 318 if (isReusable) { 319 releaseClaimedReusableContinuation() 320 } 321 return COROUTINE_SUSPENDED 322 } 323 // otherwise, onCompletionInternal was already invoked & invoked tryResume, and the result is in the state 324 if (isReusable) { 325 // release claimed reusable continuation for the future reuse 326 releaseClaimedReusableContinuation() 327 } 328 val state = this.state 329 if (state is CompletedExceptionally) throw recoverStackTrace(state.cause, this) 330 // if the parent job was already cancelled, then throw the corresponding cancellation exception 331 // otherwise, there is a race if suspendCancellableCoroutine { cont -> ... } does cont.resume(...) 332 // before the block returns. This getResult would return a result as opposed to cancellation 333 // exception that should have happened if the continuation is dispatched for execution later. 334 if (resumeMode.isCancellableMode) { 335 val job = context[Job] 336 if (job != null && !job.isActive) { 337 val cause = job.getCancellationException() 338 cancelCompletedResult(state, cause) 339 throw recoverStackTrace(cause, this) 340 } 341 } 342 return getSuccessfulResult(state) 343 } 344 345 private fun installParentHandle(): DisposableHandle? { 346 val parent = context[Job] ?: return null // don't do anything without a parent 347 // Install the handle 348 val handle = parent.invokeOnCompletion( 349 onCancelling = true, 350 handler = ChildContinuation(this).asHandler 351 ) 352 _parentHandle.compareAndSet(null, handle) 353 return handle 354 } 355 356 /** 357 * Tries to release reusable continuation. It can fail is there was an asynchronous cancellation, 358 * in which case it detaches from the parent and cancels this continuation. 359 */ 360 internal fun releaseClaimedReusableContinuation() { 361 // Cannot be cast if e.g. invoked from `installParentHandleReusable` for context without dispatchers, but with Job in it 362 val cancellationCause = (delegate as? DispatchedContinuation<*>)?.tryReleaseClaimedContinuation(this) ?: return 363 detachChild() 364 cancel(cancellationCause) 365 } 366 367 override fun resumeWith(result: Result<T>) = 368 resumeImpl(result.toState(this), resumeMode) 369 370 override fun resume(value: T, onCancellation: ((cause: Throwable) -> Unit)?) = 371 resumeImpl(value, resumeMode, onCancellation) 372 373 /** 374 * An optimized version for the code below that does not allocate 375 * a cancellation handler object and efficiently stores the specified 376 * [segment] and [index] in this [CancellableContinuationImpl]. 377 * 378 * The only difference is that `segment.onCancellation(..)` is never 379 * called if this continuation is already completed; 380 * 381 * ``` 382 * invokeOnCancellation { cause -> 383 * segment.onCancellation(index, cause) 384 * } 385 * ``` 386 */ 387 override fun invokeOnCancellation(segment: Segment<*>, index: Int) { 388 _decisionAndIndex.update { 389 check(it.index == NO_INDEX) { 390 "invokeOnCancellation should be called at most once" 391 } 392 decisionAndIndex(it.decision, index) 393 } 394 invokeOnCancellationImpl(segment) 395 } 396 397 public override fun invokeOnCancellation(handler: CompletionHandler) { 398 val cancelHandler = makeCancelHandler(handler) 399 invokeOnCancellationImpl(cancelHandler) 400 } 401 402 private fun invokeOnCancellationImpl(handler: Any) { 403 assert { handler is CancelHandler || handler is Segment<*> } 404 _state.loop { state -> 405 when (state) { 406 is Active -> { 407 if (_state.compareAndSet(state, handler)) return // quit on cas success 408 } 409 is CancelHandler, is Segment<*> -> multipleHandlersError(handler, state) 410 is CompletedExceptionally -> { 411 /* 412 * Continuation was already cancelled or completed exceptionally. 413 * NOTE: multiple invokeOnCancellation calls with different handlers are not allowed, 414 * so we check to make sure handler was installed just once. 415 */ 416 if (!state.makeHandled()) multipleHandlersError(handler, state) 417 /* 418 * Call the handler only if it was cancelled (not called when completed exceptionally). 419 * :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension, 420 * because we play type tricks on Kotlin/JS and handler is not necessarily a function there 421 */ 422 if (state is CancelledContinuation) { 423 val cause: Throwable? = (state as? CompletedExceptionally)?.cause 424 if (handler is CancelHandler) { 425 callCancelHandler(handler, cause) 426 } else { 427 val segment = handler as Segment<*> 428 callSegmentOnCancellation(segment, cause) 429 } 430 } 431 return 432 } 433 is CompletedContinuation -> { 434 /* 435 * Continuation was already completed, and might already have cancel handler. 436 */ 437 if (state.cancelHandler != null) multipleHandlersError(handler, state) 438 // Segment.invokeOnCancellation(..) does NOT need to be called on completed continuation. 439 if (handler is Segment<*>) return 440 handler as CancelHandler 441 if (state.cancelled) { 442 // Was already cancelled while being dispatched -- invoke the handler directly 443 callCancelHandler(handler, state.cancelCause) 444 return 445 } 446 val update = state.copy(cancelHandler = handler) 447 if (_state.compareAndSet(state, update)) return // quit on cas success 448 } 449 else -> { 450 /* 451 * Continuation was already completed normally, but might get cancelled while being dispatched. 452 * Change its state to CompletedContinuation, unless we have Segment which 453 * does not need to be called in this case. 454 */ 455 if (handler is Segment<*>) return 456 handler as CancelHandler 457 val update = CompletedContinuation(state, cancelHandler = handler) 458 if (_state.compareAndSet(state, update)) return // quit on cas success 459 } 460 } 461 } 462 } 463 464 private fun multipleHandlersError(handler: Any, state: Any?) { 465 error("It's prohibited to register multiple handlers, tried to register $handler, already has $state") 466 } 467 468 private fun makeCancelHandler(handler: CompletionHandler): CancelHandler = 469 if (handler is CancelHandler) handler else InvokeOnCancel(handler) 470 471 private fun dispatchResume(mode: Int) { 472 if (tryResume()) return // completed before getResult invocation -- bail out 473 // otherwise, getResult has already commenced, i.e. completed later or in other thread 474 dispatch(mode) 475 } 476 477 private fun resumedState( 478 state: NotCompleted, 479 proposedUpdate: Any?, 480 resumeMode: Int, 481 onCancellation: ((cause: Throwable) -> Unit)?, 482 idempotent: Any? 483 ): Any? = when { 484 proposedUpdate is CompletedExceptionally -> { 485 assert { idempotent == null } // there are no idempotent exceptional resumes 486 assert { onCancellation == null } // only successful results can be cancelled 487 proposedUpdate 488 } 489 !resumeMode.isCancellableMode && idempotent == null -> proposedUpdate // cannot be cancelled in process, all is fine 490 onCancellation != null || state is CancelHandler || idempotent != null -> 491 // mark as CompletedContinuation if special cases are present: 492 // Cancellation handlers that shall be called after resume or idempotent resume 493 CompletedContinuation(proposedUpdate, state as? CancelHandler, onCancellation, idempotent) 494 else -> proposedUpdate // simple case -- use the value directly 495 } 496 497 private fun resumeImpl( 498 proposedUpdate: Any?, 499 resumeMode: Int, 500 onCancellation: ((cause: Throwable) -> Unit)? = null 501 ) { 502 _state.loop { state -> 503 when (state) { 504 is NotCompleted -> { 505 val update = resumedState(state, proposedUpdate, resumeMode, onCancellation, idempotent = null) 506 if (!_state.compareAndSet(state, update)) return@loop // retry on cas failure 507 detachChildIfNonResuable() 508 dispatchResume(resumeMode) // dispatch resume, but it might get cancelled in process 509 return // done 510 } 511 is CancelledContinuation -> { 512 /* 513 * If continuation was cancelled, then resume attempt must be ignored, 514 * because cancellation is asynchronous and may race with resume. 515 * Racy exceptions will be lost, too. 516 */ 517 if (state.makeResumed()) { // check if trying to resume one (otherwise error) 518 // call onCancellation 519 onCancellation?.let { callOnCancellation(it, state.cause) } 520 return // done 521 } 522 } 523 } 524 alreadyResumedError(proposedUpdate) // otherwise, an error (second resume attempt) 525 } 526 } 527 528 /** 529 * Similar to [tryResume], but does not actually completes resume (needs [completeResume] call). 530 * Returns [RESUME_TOKEN] when resumed, `null` when it was already resumed or cancelled. 531 */ 532 private fun tryResumeImpl( 533 proposedUpdate: Any?, 534 idempotent: Any?, 535 onCancellation: ((cause: Throwable) -> Unit)? 536 ): Symbol? { 537 _state.loop { state -> 538 when (state) { 539 is NotCompleted -> { 540 val update = resumedState(state, proposedUpdate, resumeMode, onCancellation, idempotent) 541 if (!_state.compareAndSet(state, update)) return@loop // retry on cas failure 542 detachChildIfNonResuable() 543 return RESUME_TOKEN 544 } 545 is CompletedContinuation -> { 546 return if (idempotent != null && state.idempotentResume === idempotent) { 547 assert { state.result == proposedUpdate } // "Non-idempotent resume" 548 RESUME_TOKEN // resumed with the same token -- ok 549 } else { 550 null // resumed with a different token or non-idempotent -- too late 551 } 552 } 553 else -> return null // cannot resume -- not active anymore 554 } 555 } 556 } 557 558 private fun alreadyResumedError(proposedUpdate: Any?): Nothing { 559 error("Already resumed, but proposed with update $proposedUpdate") 560 } 561 562 // Unregister from parent job 563 private fun detachChildIfNonResuable() { 564 // If instance is reusable, do not detach on every reuse, #releaseInterceptedContinuation will do it for us in the end 565 if (!isReusable()) detachChild() 566 } 567 568 /** 569 * Detaches from the parent. 570 */ 571 internal fun detachChild() { 572 val handle = parentHandle ?: return 573 handle.dispose() 574 _parentHandle.value = NonDisposableHandle 575 } 576 577 // Note: Always returns RESUME_TOKEN | null 578 override fun tryResume(value: T, idempotent: Any?): Any? = 579 tryResumeImpl(value, idempotent, onCancellation = null) 580 581 override fun tryResume(value: T, idempotent: Any?, onCancellation: ((cause: Throwable) -> Unit)?): Any? = 582 tryResumeImpl(value, idempotent, onCancellation) 583 584 override fun tryResumeWithException(exception: Throwable): Any? = 585 tryResumeImpl(CompletedExceptionally(exception), idempotent = null, onCancellation = null) 586 587 // note: token is always RESUME_TOKEN 588 override fun completeResume(token: Any) { 589 assert { token === RESUME_TOKEN } 590 dispatchResume(resumeMode) 591 } 592 593 override fun CoroutineDispatcher.resumeUndispatched(value: T) { 594 val dc = delegate as? DispatchedContinuation 595 resumeImpl(value, if (dc?.dispatcher === this) MODE_UNDISPATCHED else resumeMode) 596 } 597 598 override fun CoroutineDispatcher.resumeUndispatchedWithException(exception: Throwable) { 599 val dc = delegate as? DispatchedContinuation 600 resumeImpl(CompletedExceptionally(exception), if (dc?.dispatcher === this) MODE_UNDISPATCHED else resumeMode) 601 } 602 603 @Suppress("UNCHECKED_CAST") 604 override fun <T> getSuccessfulResult(state: Any?): T = 605 when (state) { 606 is CompletedContinuation -> state.result as T 607 else -> state as T 608 } 609 610 // The exceptional state in CancellableContinuationImpl is stored directly and it is not recovered yet. 611 // The stacktrace recovery is invoked here. 612 override fun getExceptionalResult(state: Any?): Throwable? = 613 super.getExceptionalResult(state)?.let { recoverStackTrace(it, delegate) } 614 615 // For nicer debugging 616 public override fun toString(): String = 617 "${nameString()}(${delegate.toDebugString()}){$stateDebugRepresentation}@$hexAddress" 618 619 protected open fun nameString(): String = 620 "CancellableContinuation" 621 622 } 623 624 // Marker for active continuation 625 internal interface NotCompleted 626 627 private object Active : NotCompleted { toStringnull628 override fun toString(): String = "Active" 629 } 630 631 /** 632 * Base class for all [CancellableContinuation.invokeOnCancellation] handlers to avoid an extra instance 633 * on JVM, yet support JS where you cannot extend from a functional type. 634 */ 635 internal abstract class CancelHandler : CancelHandlerBase(), NotCompleted 636 637 // Wrapper for lambdas, for the performance sake CancelHandler can be subclassed directly 638 private class InvokeOnCancel( // Clashes with InvokeOnCancellation 639 private val handler: CompletionHandler 640 ) : CancelHandler() { 641 override fun invoke(cause: Throwable?) { 642 handler.invoke(cause) 643 } 644 override fun toString() = "InvokeOnCancel[${handler.classSimpleName}@$hexAddress]" 645 } 646 647 // Completed with additional metadata 648 private data class CompletedContinuation( 649 @JvmField val result: Any?, 650 @JvmField val cancelHandler: CancelHandler? = null, // installed via invokeOnCancellation 651 @JvmField val onCancellation: ((cause: Throwable) -> Unit)? = null, // installed via resume block 652 @JvmField val idempotentResume: Any? = null, 653 @JvmField val cancelCause: Throwable? = null 654 ) { 655 val cancelled: Boolean get() = cancelCause != null 656 invokeHandlersnull657 fun invokeHandlers(cont: CancellableContinuationImpl<*>, cause: Throwable) { 658 cancelHandler?.let { cont.callCancelHandler(it, cause) } 659 onCancellation?.let { cont.callOnCancellation(it, cause) } 660 } 661 } 662