@@ -319,6 +319,31 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
319319 cancelParent(cause) // tentative cancellation -- does not matter if there is no parent
320320 }
321321
322+ /* *
323+ * The method that is invoked when the job is cancelled to possibly propagate cancellation to the parent.
324+ * Returns `true` if the parent is responsible for handling the exception, `false` otherwise.
325+ *
326+ * Invariant: never returns `false` for instances of [CancellationException], otherwise such exception
327+ * may leak to the [CoroutineExceptionHandler].
328+ */
329+ private fun cancelParent (cause : Throwable ): Boolean {
330+ /* CancellationException is considered "normal" and parent usually is not cancelled when child produces it.
331+ * This allow parent to cancel its children (normally) without being cancelled itself, unless
332+ * child crashes and produce some other exception during its completion.
333+ */
334+ val isCancellation = cause is CancellationException
335+ val parent = parentHandle
336+ // No parent -- ignore CE, report other exceptions.
337+ if (parent == = null || parent == = NonDisposableHandle ) {
338+ return isCancellation
339+ }
340+
341+ // Is scoped coroutine -- don't propagate, will be rethrown
342+ if (isScopedCoroutine) return isCancellation
343+ // Notify parent but don't forget to check cancellation
344+ return parent.childCancelled(cause) || isCancellation
345+ }
346+
322347 private fun NodeList.notifyCompletion (cause : Throwable ? ) =
323348 notifyHandlers<JobNode <* >>(this , cause)
324349
@@ -594,21 +619,29 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
594619 cancelImpl(parentJob)
595620 }
596621
597- // Child was cancelled with cause
598- // It is overridden in supervisor implementations to ignore child cancellation
599- public open fun childCancelled (cause : Throwable ): Boolean =
600- cancelImpl(cause) && handlesException
622+ /* *
623+ * Child was cancelled with a cause.
624+ * In this method parent decides whether it cancels itself (e.g. on a critical failure) and whether it handles the exception of the child.
625+ * It is overridden in supervisor implementations to completely ignore any child cancellation.
626+ * Returns `true` if exception is handled, `false` otherwise (then caller is responsible for handling an exception)
627+ *
628+ * Invariant: never returns `false` for instances of [CancellationException], otherwise such exception
629+ * may leak to the [CoroutineExceptionHandler].
630+ */
631+ public open fun childCancelled (cause : Throwable ): Boolean {
632+ if (cause is CancellationException ) return true
633+ return cancelImpl(cause) && handlesException
634+ }
601635
602636 /* *
603637 * Makes this [Job] cancelled with a specified [cause].
604638 * It is used in [AbstractCoroutine]-derived classes when there is an internal failure.
605639 */
606- public fun cancelCoroutine (cause : Throwable ? ) =
607- cancelImpl(cause)
640+ public fun cancelCoroutine (cause : Throwable ? ) = cancelImpl(cause)
608641
609642 // cause is Throwable or ParentJob when cancelChild was invoked
610643 // returns true is exception was handled, false otherwise
611- private fun cancelImpl (cause : Any? ): Boolean {
644+ internal fun cancelImpl (cause : Any? ): Boolean {
612645 if (onCancelComplete) {
613646 // make sure it is completing, if cancelMakeCompleting returns true it means it had make it
614647 // completing and had recorded exception
@@ -912,14 +945,12 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
912945 protected open fun onCancelling (cause : Throwable ? ) {}
913946
914947 /* *
915- * When this function returns `true` the parent is cancelled on cancellation of this job.
916- * Note that [CancellationException] is considered "normal" and parent is not cancelled when child produces it.
917- * This allows parent to cancel its children (normally) without being cancelled itself, unless
918- * child crashes and produce some other exception during its completion.
919- *
920- * @suppress **This is unstable API and it is subject to change.*
948+ * Returns `true` for scoped coroutines.
949+ * Scoped coroutine is a coroutine that is executed sequentially within the enclosing scope without any concurrency.
950+ * Scoped coroutines always handle any exception happened within -- they just rethrow it to the enclosing scope.
951+ * Examples of scoped coroutines are `coroutineScope`, `withTimeout` and `runBlocking`.
921952 */
922- protected open val cancelsParent : Boolean get() = true
953+ protected open val isScopedCoroutine : Boolean get() = false
923954
924955 /* *
925956 * Returns `true` for jobs that handle their exceptions or integrate them into the job's result via [onCompletionInternal].
@@ -939,20 +970,9 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
939970 *
940971 * This method is invoked **exactly once** when the final exception of the job is determined
941972 * and before it becomes complete. At the moment of invocation the job and all its children are complete.
942- *
943- * @suppress **This is unstable API and it is subject to change.*
944973 */
945974 protected open fun handleJobException (exception : Throwable ): Boolean = false
946975
947- private fun cancelParent (cause : Throwable ): Boolean {
948- // CancellationException is considered "normal" and parent is not cancelled when child produces it.
949- // This allow parent to cancel its children (normally) without being cancelled itself, unless
950- // child crashes and produce some other exception during its completion.
951- if (cause is CancellationException ) return true
952- if (! cancelsParent) return false
953- return parentHandle?.childCancelled(cause) == true
954- }
955-
956976 /* *
957977 * Override for completion actions that need to update some external object depending on job's state,
958978 * right before all the waiters for coroutine's completion are notified.
0 commit comments