@@ -20,7 +20,7 @@ import kotlin.coroutines.experimental.intrinsics.*
2020 * @param active when `true` the job is created in _active_ state, when `false` in _new_ state. See [Job] for details.
2121 * @suppress **This is unstable API and it is subject to change.**
2222 */
23- internal open class JobSupport constructor(active : Boolean ) : Job, ChildJob, SelectClause0 {
23+ internal open class JobSupport constructor(active : Boolean ) : Job, ChildJob, ParentJob, SelectClause0 {
2424 final override val key: CoroutineContext .Key <* > get() = Job
2525
2626 /*
@@ -230,63 +230,23 @@ internal open class JobSupport constructor(active: Boolean) : Job, ChildJob, Sel
230230 if (state.isCancelling) return createJobCancellationException()
231231 return null
232232 }
233- /*
234- * This is a place where we step on our API limitation:
235- * We can't distinguish internal JobCancellationException from our parent
236- * from external cancellation, thus we ought to collect all exceptions.
237- * If parent is cancelling, it cancels its children with JCE(rootCause).
238- * When child is building final exception, it can skip JCE(anything) if it knows
239- * that parent handles exceptions, because parent should already have this exception.
240- * If parent does not, then we should unwrap exception, otherwise in the following code
241- * ```
242- * val parent = Job()
243- * launch(parent) {
244- * try { delay() } finally { throw E2() }
245- * }
246- * parent.cancel(E1)
247- * ```
248- * E1 will be lost.
249- */
250- var rootCause = exceptions[0 ]
251- if (rootCause is CancellationException ) {
252- val cause = unwrap(rootCause)
253- rootCause = if (cause != = null ) {
254- cause
255- } else {
256- exceptions.firstOrNull { unwrap(it) != null } ? : return rootCause
257- }
258- }
259- return rootCause
233+ // Take either the first real exception (not a cancellation) or just the first exception
234+ return exceptions.firstOrNull { it !is CancellationException } ? : exceptions[0 ]
260235 }
261236
262237 private fun suppressExceptions (rootCause : Throwable , exceptions : List <Throwable >): Boolean {
263238 if (exceptions.size <= 1 ) return false // nothing more to do here
264239 val seenExceptions = identitySet<Throwable >(exceptions.size)
265240 var suppressed = false
266- for (i in 1 until exceptions.size) {
267- val unwrapped = unwrap(exceptions[i])
268- if (unwrapped != = null && unwrapped != = rootCause) {
269- if (seenExceptions.add(unwrapped)) {
270- rootCause.addSuppressedThrowable(unwrapped)
271- suppressed = true
272- }
241+ for (exception in exceptions) {
242+ if (exception != = rootCause && exception !is CancellationException && seenExceptions.add(exception)) {
243+ rootCause.addSuppressedThrowable(exception)
244+ suppressed = true
273245 }
274246 }
275247 return suppressed
276248 }
277249
278- private tailrec fun unwrap (exception : Throwable ): Throwable ? {
279- if (exception is CancellationException && parentHandlesExceptions) {
280- return null
281- }
282- return if (exception is CancellationException ) {
283- val cause = exception.cause
284- if (cause != = null ) unwrap(cause) else null
285- } else {
286- exception
287- }
288- }
289-
290250 // fast-path method to finalize normally completed coroutines without children
291251 private fun tryFinalizeSimpleState (state : Incomplete , update : Any? , mode : Int ): Boolean {
292252 check(state is Empty || state is JobNode <* >) // only simple state without lists where children can concurrently add
@@ -616,15 +576,15 @@ internal open class JobSupport constructor(active: Boolean) : Job, ChildJob, Sel
616576 cancelImpl(cause) && handlesException
617577
618578 // Parent is cancelling child
619- public final override fun parentCancelled (parentJob : Job ) {
579+ public final override fun parentCancelled (parentJob : ParentJob ) {
620580 cancelImpl(parentJob)
621581 }
622582
623583 // Child was cancelled with cause
624584 public open fun childCancelled (cause : Throwable ): Boolean =
625585 cancelImpl(cause) && handlesException
626586
627- // cause is Throwable or Job when cancelChild was invoked
587+ // cause is Throwable or ParentJob when cancelChild was invoked
628588 // returns true is exception was handled, false otherwise
629589 private fun cancelImpl (cause : Any? ): Boolean {
630590 if (onCancelComplete) {
@@ -636,6 +596,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, ChildJob, Sel
636596 return makeCancelling(cause)
637597 }
638598
599+ // cause is Throwable or ParentJob when cancelChild was invoked
639600 private fun cancelMakeCompleting (cause : Any? ): Boolean {
640601 loopOnState { state ->
641602 if (state !is Incomplete || state is Finishing && state.isCompleting) {
@@ -654,14 +615,35 @@ internal open class JobSupport constructor(active: Boolean) : Job, ChildJob, Sel
654615 private fun createJobCancellationException () =
655616 JobCancellationException (" Job was cancelled" , null , this )
656617
657- // cause is Throwable or Job when cancelChild was invoked, cause can be null only on cancel
618+ override fun getChildJobCancellationCause (): Throwable {
619+ // determine root cancellation cause of this job (why is it cancelling its children?)
620+ val state = this .state
621+ val rootCause = when (state) {
622+ is Finishing -> state.rootCause
623+ is Incomplete -> error(" Cannot be cancelling child in this state: $state " )
624+ is CompletedExceptionally -> state.cause
625+ else -> null // create exception with the below code on normal completion
626+ }
627+ /*
628+ * If this parent job handles exceptions, then wrap cause into JobCancellationException, because we
629+ * don't want the child to handle this exception on more time. Otherwise, pass our original rootCause
630+ * to the child for cancellation.
631+ */
632+ return if (rootCause == null || handlesException && rootCause !is CancellationException ) {
633+ JobCancellationException (" Parent job is ${stateString(state)} " , rootCause, this )
634+ } else {
635+ rootCause
636+ }
637+ }
638+
639+ // cause is Throwable or ParentJob when cancelChild was invoked
658640 private fun createCauseException (cause : Any? ): Throwable = when (cause) {
659641 is Throwable ? -> cause ? : createJobCancellationException()
660- else -> (cause as Job ).getCancellationException ()
642+ else -> (cause as ParentJob ).getChildJobCancellationCause ()
661643 }
662644
663645 // transitions to Cancelling state
664- // cause is Throwable or Job when cancelChild was invoked, cause can be null only on cancel
646+ // cause is Throwable or ParentJob when cancelChild was invoked
665647 private fun makeCancelling (cause : Any? ): Boolean {
666648 var causeExceptionCache: Throwable ? = null // lazily init result of createCauseException(cause)
667649 loopOnState { state ->
@@ -927,10 +909,6 @@ internal open class JobSupport constructor(active: Boolean) : Job, ChildJob, Sel
927909 */
928910 protected open val handlesException: Boolean get() = true
929911
930- // returns true when we know that parent handles exceptions
931- private val parentHandlesExceptions: Boolean get() =
932- (parentHandle as ? ChildHandleNode )?.job?.handlesException ? : false
933-
934912 /* *
935913 * This method is invoked **exactly once** when the final exception of the job is determined
936914 * and before it becomes complete. At the moment of invocation the job and all its children are complete.
@@ -959,27 +937,22 @@ internal open class JobSupport constructor(active: Boolean) : Job, ChildJob, Sel
959937
960938 // for nicer debugging
961939 public override fun toString (): String =
962- " ${nameString()} {${stateString()} }@$hexAddress "
940+ " ${nameString()} {${stateString(state )} }@$hexAddress "
963941
964942 /* *
965943 * @suppress **This is unstable API and it is subject to change.**
966944 */
967945 internal open fun nameString (): String = classSimpleName
968946
969- private fun stateString (): String {
970- val state = this .state
971- return when (state) {
972- is Finishing -> buildString {
973- when {
974- state.isCancelling -> append(" Cancelling" )
975- else -> append(" Active" )
976- }
977- if (state.isCompleting) append(" Completing" )
978- }
979- is Incomplete -> if (state.isActive) " Active" else " New"
980- is CompletedExceptionally -> " Cancelled"
981- else -> " Completed"
947+ private fun stateString (state : Any? ): String = when (state) {
948+ is Finishing -> when {
949+ state.isCancelling -> " Cancelling"
950+ state.isCompleting -> " Completing"
951+ else -> " Active"
982952 }
953+ is Incomplete -> if (state.isActive) " Active" else " New"
954+ is CompletedExceptionally -> " Cancelled"
955+ else -> " Completed"
983956 }
984957
985958 // Completing & Cancelling states,
@@ -1068,7 +1041,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, ChildJob, Sel
10681041 delegate : Continuation <T >,
10691042 private val job : JobSupport
10701043 ) : CancellableContinuationImpl<T>(delegate, MODE_CANCELLABLE ) {
1071- override fun getParentCancellationCause (parent : Job ): Throwable {
1044+ override fun getContinuationCancellationCause (parent : Job ): Throwable {
10721045 val state = job.state
10731046 /*
10741047 * When the job we are waiting for had already completely completed exceptionally or
@@ -1351,7 +1324,7 @@ internal class ChildContinuation(
13511324 @JvmField val child : AbstractContinuation <* >
13521325) : JobCancellingNode<Job>(parent) {
13531326 override fun invoke (cause : Throwable ? ) {
1354- child.cancelImpl(child.getParentCancellationCause (job))
1327+ child.cancelImpl(child.getContinuationCancellationCause (job))
13551328 }
13561329 override fun toString (): String =
13571330 " ChildContinuation[$child ]"
0 commit comments