@@ -20,7 +20,7 @@ import kotlin.coroutines.experimental.intrinsics.*
20
20
* @param active when `true` the job is created in _active_ state, when `false` in _new_ state. See [Job] for details.
21
21
* @suppress **This is unstable API and it is subject to change.**
22
22
*/
23
- internal open class JobSupport constructor(active : Boolean ) : Job, ChildJob, SelectClause0 {
23
+ internal open class JobSupport constructor(active : Boolean ) : Job, ChildJob, ParentJob, SelectClause0 {
24
24
final override val key: CoroutineContext .Key <* > get() = Job
25
25
26
26
/*
@@ -230,63 +230,23 @@ internal open class JobSupport constructor(active: Boolean) : Job, ChildJob, Sel
230
230
if (state.isCancelling) return createJobCancellationException()
231
231
return null
232
232
}
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 ]
260
235
}
261
236
262
237
private fun suppressExceptions (rootCause : Throwable , exceptions : List <Throwable >): Boolean {
263
238
if (exceptions.size <= 1 ) return false // nothing more to do here
264
239
val seenExceptions = identitySet<Throwable >(exceptions.size)
265
240
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
273
245
}
274
246
}
275
247
return suppressed
276
248
}
277
249
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
-
290
250
// fast-path method to finalize normally completed coroutines without children
291
251
private fun tryFinalizeSimpleState (state : Incomplete , update : Any? , mode : Int ): Boolean {
292
252
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
616
576
cancelImpl(cause) && handlesException
617
577
618
578
// Parent is cancelling child
619
- public final override fun parentCancelled (parentJob : Job ) {
579
+ public final override fun parentCancelled (parentJob : ParentJob ) {
620
580
cancelImpl(parentJob)
621
581
}
622
582
623
583
// Child was cancelled with cause
624
584
public open fun childCancelled (cause : Throwable ): Boolean =
625
585
cancelImpl(cause) && handlesException
626
586
627
- // cause is Throwable or Job when cancelChild was invoked
587
+ // cause is Throwable or ParentJob when cancelChild was invoked
628
588
// returns true is exception was handled, false otherwise
629
589
private fun cancelImpl (cause : Any? ): Boolean {
630
590
if (onCancelComplete) {
@@ -636,6 +596,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, ChildJob, Sel
636
596
return makeCancelling(cause)
637
597
}
638
598
599
+ // cause is Throwable or ParentJob when cancelChild was invoked
639
600
private fun cancelMakeCompleting (cause : Any? ): Boolean {
640
601
loopOnState { state ->
641
602
if (state !is Incomplete || state is Finishing && state.isCompleting) {
@@ -654,14 +615,35 @@ internal open class JobSupport constructor(active: Boolean) : Job, ChildJob, Sel
654
615
private fun createJobCancellationException () =
655
616
JobCancellationException (" Job was cancelled" , null , this )
656
617
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
658
640
private fun createCauseException (cause : Any? ): Throwable = when (cause) {
659
641
is Throwable ? -> cause ? : createJobCancellationException()
660
- else -> (cause as Job ).getCancellationException ()
642
+ else -> (cause as ParentJob ).getChildJobCancellationCause ()
661
643
}
662
644
663
645
// 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
665
647
private fun makeCancelling (cause : Any? ): Boolean {
666
648
var causeExceptionCache: Throwable ? = null // lazily init result of createCauseException(cause)
667
649
loopOnState { state ->
@@ -927,10 +909,6 @@ internal open class JobSupport constructor(active: Boolean) : Job, ChildJob, Sel
927
909
*/
928
910
protected open val handlesException: Boolean get() = true
929
911
930
- // returns true when we know that parent handles exceptions
931
- private val parentHandlesExceptions: Boolean get() =
932
- (parentHandle as ? ChildHandleNode )?.job?.handlesException ? : false
933
-
934
912
/* *
935
913
* This method is invoked **exactly once** when the final exception of the job is determined
936
914
* 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
959
937
960
938
// for nicer debugging
961
939
public override fun toString (): String =
962
- " ${nameString()} {${stateString()} }@$hexAddress "
940
+ " ${nameString()} {${stateString(state )} }@$hexAddress "
963
941
964
942
/* *
965
943
* @suppress **This is unstable API and it is subject to change.**
966
944
*/
967
945
internal open fun nameString (): String = classSimpleName
968
946
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"
982
952
}
953
+ is Incomplete -> if (state.isActive) " Active" else " New"
954
+ is CompletedExceptionally -> " Cancelled"
955
+ else -> " Completed"
983
956
}
984
957
985
958
// Completing & Cancelling states,
@@ -1068,7 +1041,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, ChildJob, Sel
1068
1041
delegate : Continuation <T >,
1069
1042
private val job : JobSupport
1070
1043
) : CancellableContinuationImpl<T>(delegate, MODE_CANCELLABLE ) {
1071
- override fun getParentCancellationCause (parent : Job ): Throwable {
1044
+ override fun getContinuationCancellationCause (parent : Job ): Throwable {
1072
1045
val state = job.state
1073
1046
/*
1074
1047
* When the job we are waiting for had already completely completed exceptionally or
@@ -1351,7 +1324,7 @@ internal class ChildContinuation(
1351
1324
@JvmField val child : AbstractContinuation <* >
1352
1325
) : JobCancellingNode<Job>(parent) {
1353
1326
override fun invoke (cause : Throwable ? ) {
1354
- child.cancelImpl(child.getParentCancellationCause (job))
1327
+ child.cancelImpl(child.getContinuationCancellationCause (job))
1355
1328
}
1356
1329
override fun toString (): String =
1357
1330
" ChildContinuation[$child ]"
0 commit comments