graph LR
协程内部trycatch-->|非suspend方法|作为invokeSuspend内部状态机中的一个分支,分支内和非协程类似catch,协程无感知
协程内部trycatch-->|suspend方法|抛出异常("resume流程invokeSuspend时ResultKt.throwOnFailure($result);抛出异常")
CancellationException-->参考协程取消
协程UncaughtException
/** kotlin/util/Result.kt
* Throws exception if the result is failure. This internal function minimizes
* inlined bytecode for [getOrThrow] and makes sure that in the future we can
* add some exception-augmenting logic here (if needed).
*/
@PublishedApi
@SinceKotlin("1.3")
internal fun Result<*>.throwOnFailure() {
if (value is Result.Failure) throw value.exception
}
internal abstract class BaseContinuationImpl(
// This is `public val` so that it is private on JVM and cannot be modified by untrusted code, yet
// it has a public getter (since even untrusted code is allowed to inspect its call stack).
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
// This implementation is final. This fact is used to unroll resumeWith recursion.
public final override fun resumeWith(result: Result<Any?>) {
// This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
var current = this
var param = result
while (true) {
// Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
// can precisely track what part of suspended callstack was already resumed
probeCoroutineResumed(current)
with(current) {
val completion = completion!! // fail fast when trying to resume continuation without completion
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)//main
}
releaseIntercepted() // this state machine instance is terminating
if (completion is BaseContinuationImpl) {
// unrolling recursion via loop
current = completion
param = outcome
} else {
// top-level completion reached -- invoke and return
completion.resumeWith(outcome)//main
return
}
}
}
}
public abstract class AbstractCoroutine<in T>
/**
* Completes execution of this with coroutine with the specified result.
*/
public final override fun resumeWith(result: Result<T>) {
makeCompletingOnce(result.toState(), defaultResumeMode)
}
public open class JobSupport constructor(active: Boolean) : Job, ChildJob, ParentJob, SelectClause0 {
internal fun makeCompletingOnce(proposedUpdate: Any?, mode: Int): Boolean = loopOnState { state ->
when (tryMakeCompleting(state, proposedUpdate, mode)) {
COMPLETING_ALREADY_COMPLETING -> throw IllegalStateException("Job $this is already complete or completing, " +
"but is being completed with $proposedUpdate", proposedUpdate.exceptionOrNull)
COMPLETING_COMPLETED -> return true
COMPLETING_WAITING_CHILDREN -> return false
COMPLETING_RETRY -> return@loopOnState
else -> error("unexpected result")
}
}
private fun tryMakeCompleting(state: Any?, proposedUpdate: Any?, mode: Int): Int {
if (state !is Incomplete)
return COMPLETING_ALREADY_COMPLETING
/*
* FAST PATH -- no children to wait for && simple state (no list) && not cancelling => can complete immediately
* Cancellation (failures) always have to go through Finishing state to serialize exception handling.
* Otherwise, there can be a race between (completed state -> handled exception and newly attached child/join)
* which may miss unhandled exception.
*/
if ((state is Empty || state is JobNode<*>) && state !is ChildHandleNode && proposedUpdate !is CompletedExceptionally) {
if (!tryFinalizeSimpleState(state, proposedUpdate, mode)) return COMPLETING_RETRY
return COMPLETING_COMPLETED
}
// The separate slow-path function to simplify profiling
return tryMakeCompletingSlowPath(state, proposedUpdate, mode)//main
}
private fun tryMakeCompletingSlowPath(state: Incomplete, proposedUpdate: Any?, mode: Int): Int {
// get state's list or else promote to list to correctly operate on child lists
val list = getOrPromoteCancellingList(state) ?: return COMPLETING_RETRY
// promote to Finishing state if we are not in it yet
// This promotion has to be atomic w.r.t to state change, so that a coroutine that is not active yet
// atomically transition to finishing & completing state
val finishing = state as? Finishing ?: Finishing(list, false, null)
// must synchronize updates to finishing state
var notifyRootCause: Throwable? = null
synchronized(finishing) {
// check if this state is already completing
if (finishing.isCompleting) return COMPLETING_ALREADY_COMPLETING
// mark as completing
finishing.isCompleting = true
// if we need to promote to finishing then atomically do it here.
// We do it as early is possible while still holding the lock. This ensures that we cancelImpl asap
// (if somebody else is faster) and we synchronize all the threads on this finishing lock asap.
if (finishing !== state) {
if (!_state.compareAndSet(state, finishing)) return COMPLETING_RETRY
}
// ## IMPORTANT INVARIANT: Only one thread (that had set isCompleting) can go past this point
require(!finishing.isSealed) // cannot be sealed
// add new proposed exception to the finishing state
val wasCancelling = finishing.isCancelling
(proposedUpdate as? CompletedExceptionally)?.let { finishing.addExceptionLocked(it.cause) }
// If it just becomes cancelling --> must process cancelling notifications
notifyRootCause = finishing.rootCause.takeIf { !wasCancelling }//cancel异常时notifyRootCause为null,不会notifyCancelling
}
// process cancelling notification here -- it cancels all the children _before_ we start to to wait them (sic!!!)
notifyRootCause?.let { notifyCancelling(list, it) }
// now wait for children
val child = firstChild(state)
if (child != null && tryWaitForChild(finishing, child, proposedUpdate))
return COMPLETING_WAITING_CHILDREN
// otherwise -- we have not children left (all were already cancelled?)
if (tryFinalizeFinishingState(finishing, proposedUpdate, mode))
return COMPLETING_COMPLETED
// otherwise retry
return COMPLETING_RETRY
}
// Finalizes Finishing -> Completed (terminal state) transition.
// ## IMPORTANT INVARIANT: Only one thread can be concurrently invoking this method.
private fun tryFinalizeFinishingState(state: Finishing, proposedUpdate: Any?, mode: Int): Boolean {
/*
* Note: proposed state can be Incomplete, e.g.
* async {
* something.invokeOnCompletion {} // <- returns handle which implements Incomplete under the hood
* }
*/
require(this.state === state) // consistency check -- it cannot change
require(!state.isSealed) // consistency check -- cannot be sealed yet
require(state.isCompleting) // consistency check -- must be marked as completing
val proposedException = (proposedUpdate as? CompletedExceptionally)?.cause
// Create the final exception and seal the state so that no more exceptions can be added
var wasCancelling = false // KLUDGE: we cannot have contract for our own expect fun synchronized
val finalException = synchronized(state) {
wasCancelling = state.isCancelling
val exceptions = state.sealLocked(proposedException)
val finalCause = getFinalRootCause(state, exceptions)
if (finalCause != null) addSuppressedExceptions(finalCause, exceptions)
finalCause
}
// Create the final state object
val finalState = when {
// was not cancelled (no exception) -> use proposed update value
finalException == null -> proposedUpdate
// small optimization when we can used proposeUpdate object as is on cancellation
finalException === proposedException -> proposedUpdate
// cancelled job final state
else -> CompletedExceptionally(finalException)
}
// Now handle the final exception
if (finalException != null) {
val handled = cancelParent(finalException) || handleJobException(finalException)//main,handleJobException
if (handled) (finalState as CompletedExceptionally).makeHandled()
}
// Process state updates for the final state before the state of the Job is actually set to the final state
// to avoid races where outside observer may see the job in the final state, yet exception is not handled yet.
if (!wasCancelling) onCancelling(finalException)
onCompletionInternal(finalState)
// Then CAS to completed state -> it must succeed
require(_state.compareAndSet(state, finalState.boxIncomplete())) { "Unexpected state: ${_state.value}, expected: $state, update: $finalState" }
// And process all post-completion actions
completeStateFinalization(state, finalState, mode)
return true
}
/**
* The method that is invoked when the job is cancelled to possibly propagate cancellation to the parent.
* Returns `true` if the parent is responsible for handling the exception, `false` otherwise.
*
* Invariant: never returns `false` for instances of [CancellationException], otherwise such exception
* may leak to the [CoroutineExceptionHandler].
*/
private fun cancelParent(cause: Throwable): Boolean {
// Is scoped coroutine -- don't propagate, will be rethrown
if (isScopedCoroutine) return true
/* CancellationException is considered "normal" and parent usually is not cancelled when child produces it.
* This allow parent to cancel its children (normally) without being cancelled itself, unless
* child crashes and produce some other exception during its completion.
*/
val isCancellation = cause is CancellationException
val parent = parentHandle
// No parent -- ignore CE, report other exceptions.
if (parent === null || parent === NonDisposableHandle) {
return isCancellation//仅cancel时parent === NonDisposableHandle,return true
}
// Notify parent but don't forget to check cancellation
return parent.childCancelled(cause) || isCancellation
}
private open class StandaloneCoroutine(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine<Unit>(parentContext, active) {
override fun handleJobException(exception: Throwable): Boolean {
handleCoroutineException(context, exception)
return true
}
}
commonMain/CoroutineExceptionHandler.kt
/**
* Helper function for coroutine builder implementations to handle uncaught and unexpected exceptions in coroutines,
* that could not be otherwise handled in a normal way through structured concurrency, saving them to a future, and
* cannot be rethrown. This is a last resort handler to prevent lost exceptions.
*
* If there is [CoroutineExceptionHandler] in the context, then it is used. If it throws an exception during handling
* or is absent, all instances of [CoroutineExceptionHandler] found via [ServiceLoader] and
* [Thread.uncaughtExceptionHandler] are invoked.
*/
@InternalCoroutinesApi
public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
// Invoke an exception handler from the context if present
try {
context[CoroutineExceptionHandler]?.let {
it.handleException(context, exception)
return
}
} catch (t: Throwable) {
handleCoroutineExceptionImpl(context, handlerException(exception, t))
return
}
// If a handler is not present in the context or an exception was thrown, fallback to the global handler
handleCoroutineExceptionImpl(context, exception)
}
jvmMain/CoroutineExceptionHandlerImpl.kt
internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
// use additional extension handlers
for (handler in handlers) {
try {
handler.handleException(context, exception)
} catch (t: Throwable) {
// Use thread's handler if custom handler failed to handle exception
val currentThread = Thread.currentThread()
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, handlerException(exception, t))
}
}
// use thread's handler
val currentThread = Thread.currentThread()
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
}
/**
* A list of globally installed [CoroutineExceptionHandler] instances.
*
* Note that Android may have dummy [Thread.contextClassLoader] which is used by one-argument [ServiceLoader.load] function,
* see (https://stackoverflow.com/questions/13407006/android-class-loader-may-fail-for-processes-that-host-multiple-applications).
* So here we explicitly use two-argument `load` with a class-loader of [CoroutineExceptionHandler] class.
*
* We are explicitly using the `ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()`
* form of the ServiceLoader call to enable R8 optimization when compiled on Android.
*/
private val handlers: List<CoroutineExceptionHandler> = ServiceLoader.load(
CoroutineExceptionHandler::class.java,
CoroutineExceptionHandler::class.java.classLoader
).iterator().asSequence().toList()
@Keep
internal class AndroidExceptionPreHandler :
AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler, Function0<Method?> {
override fun handleException(context: CoroutineContext, exception: Throwable) {
/*
* If we are on old SDK, then use Android's `Thread.getUncaughtExceptionPreHandler()` that ensures that
* an exception is logged before crashing the application.
*
* Since Android Pie default uncaught exception handler always ensures that exception is logged without interfering with
* pre-handler, so reflection hack is no longer needed.
*
* See https://android-review.googlesource.com/c/platform/frameworks/base/+/654578/
*/
val thread = Thread.currentThread()
if (Build.VERSION.SDK_INT >= 28) {
thread.uncaughtExceptionHandler.uncaughtException(thread, exception)//main
} else {
(preHandler?.invoke(null) as? Thread.UncaughtExceptionHandler)
?.uncaughtException(thread, exception)
}
}
Thread
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}