kotlin协程异常

协程内部异常分类

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
}

协程UncaughtException

标记异常

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
                }
            }
        }
    }
AbstractCoroutine.resumeWith
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)
}
JobSupport.makeCompletingOnce
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
}
tryFinalizeFinishingState
// 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
}

处理异常

StandaloneCoroutine.handleJobException
private open class StandaloneCoroutine(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine<Unit>(parentContext, active) {
    override fun handleJobException(exception: Throwable): Boolean {
        handleCoroutineException(context, exception)
        return true
    }
}
context[CoroutineExceptionHandler].handleException

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)
}
handleCoroutineExceptionImpl

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()
AndroidExceptionPreHandler.handleException
@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;
}