@@ -52,7 +52,7 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
52
52
* | _Completing_ (optional transient state) | `true` | `false` | `false` |
53
53
* | _Cancelling_ (optional transient state) | `false` | `false` | `true` |
54
54
* | _Cancelled_ (final state) | `false` | `true` | `true` |
55
- * | _Completed normally_ (final state) | `false` | `true` | `false` |
55
+ * | _Completed_ (final state) | `false` | `true` | `false` |
56
56
*
57
57
* Usually, a job is created in _active_ state (it is created and started). However, coroutine builders
58
58
* that provide an optional `start` parameter create a coroutine in _new_ state when this parameter is set to
@@ -68,8 +68,8 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
68
68
* wait children
69
69
* +-----+ start +--------+ complete +-------------+ finish +-----------+
70
70
* | New | ---------------> | Active | -----------> | Completing | -------> | Completed |
71
- * +-----+ +--------+ +-------------+ | normally |
72
- * | | | +-----------+
71
+ * +-----+ +--------+ +-------------+ +-----------+
72
+ * | | |
73
73
* | cancel | cancel | cancel
74
74
* V V |
75
75
* +-----------+ finish +------------+ |
@@ -82,7 +82,7 @@ import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
82
82
* A job is active while the coroutine is working and job's cancellation aborts the coroutine when
83
83
* the coroutine is suspended on a _cancellable_ suspension point by throwing [CancellationException].
84
84
*
85
- * A job can have a _parent_ job. A job with a parent is cancelled when its parent is cancelled or completes.
85
+ * A job can have a _parent_ job. A job with a parent is cancelled when its parent is cancelled or completes exceptionally .
86
86
* Parent job waits for all its [children][attachChild] to complete in _completing_ or _cancelling_ state.
87
87
* _Completing_ state is purely internal to the job. For an outside observer a _completing_ job is still active,
88
88
* while internally it is waiting for its children.
@@ -185,9 +185,10 @@ public interface Job : CoroutineContext.Element {
185
185
* returns a handle that should be used to detach it.
186
186
*
187
187
* A parent-child relation has the following effect:
188
- * * Cancellation of parent with [cancel] immediately cancels all its children with the same cause.
188
+ * * Cancellation of parent with [cancel] or its exceptional completion (failure)
189
+ * immediately cancels all its children.
189
190
* * Parent cannot complete until all its children are complete. Parent waits for all its children to
190
- * complete first in _completing_ or _cancelling_ state.
191
+ * complete in _completing_ or _cancelling_ state.
191
192
*
192
193
* A child must store the resulting [DisposableHandle] and [dispose][DisposableHandle.dispose] the attachment
193
194
* to its parent on its own completion.
@@ -988,30 +989,38 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
988
989
throw IllegalStateException (" Job $this is already complete, but is being completed with $proposedUpdate " , proposedUpdate.exceptionOrNull)
989
990
if (state is Finishing && state.completing)
990
991
throw IllegalStateException (" Job $this is already completing, but is being completed with $proposedUpdate " , proposedUpdate.exceptionOrNull)
991
- val waitChild : Child = firstChild(state) ? : // or else complete immediately
992
+ val child : Child = firstChild(state) ? : // or else complete immediately w/o children
992
993
if (updateState(state, proposedUpdate, mode)) return true else return @loopOnState
993
- // switch to completing state
994
+ // must promote to list to correct operate on child lists
994
995
if (state is JobNode <* >) {
995
- // must promote to list to make completing & retry
996
996
promoteSingleToNodeList(state)
997
- } else {
998
- val completing = Finishing (state.list!! , (state as ? Finishing )?.cancelled, true )
999
- if (_state .compareAndSet(state, completing)) {
1000
- waitForChild(waitChild, proposedUpdate)
1001
- return false
1002
- }
997
+ return @loopOnState // retry
998
+ }
999
+ // cancel all children in list on exceptional completion
1000
+ if (proposedUpdate is CompletedExceptionally )
1001
+ child.cancelChildrenInternal(proposedUpdate.exception)
1002
+ // switch to completing state
1003
+ val completing = Finishing (state.list!! , (state as ? Finishing )?.cancelled, true )
1004
+ if (_state .compareAndSet(state, completing)) {
1005
+ waitForChild(child, proposedUpdate)
1006
+ return false
1003
1007
}
1004
1008
}
1005
1009
}
1006
1010
1011
+ private tailrec fun Child.cancelChildrenInternal (cause : Throwable ) {
1012
+ childJob.cancel(JobCancellationException (" Child job was cancelled because of parent failure" , cause, childJob))
1013
+ nextChild()?.cancelChildrenInternal(cause)
1014
+ }
1015
+
1007
1016
private val Any? .exceptionOrNull: Throwable ?
1008
1017
get() = (this as ? CompletedExceptionally )?.exception
1009
1018
1010
1019
private fun firstChild (state : Incomplete ) =
1011
1020
state as ? Child ? : state.list?.nextChild()
1012
1021
1013
- private fun waitForChild (waitChild : Child , proposedUpdate : Any? ) {
1014
- waitChild. child.invokeOnCompletion(handler = ChildCompletion (this , waitChild , proposedUpdate))
1022
+ private fun waitForChild (child : Child , proposedUpdate : Any? ) {
1023
+ child.childJob. invokeOnCompletion(handler = ChildCompletion (this , child , proposedUpdate))
1015
1024
}
1016
1025
1017
1026
internal fun continueCompleting (lastChild : Child , proposedUpdate : Any? ) {
@@ -1044,13 +1053,13 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
1044
1053
public override fun cancelChildren (cause : Throwable ? ) {
1045
1054
val state = this .state
1046
1055
when (state) {
1047
- is Child -> state.child .cancel(cause)
1056
+ is Child -> state.childJob .cancel(cause)
1048
1057
is Incomplete -> state.list?.cancelChildrenList(cause)
1049
1058
}
1050
1059
}
1051
1060
1052
1061
private fun NodeList.cancelChildrenList (cause : Throwable ? ) {
1053
- forEach<Child > { it.child .cancel(cause) }
1062
+ forEach<Child > { it.childJob .cancel(cause) }
1054
1063
}
1055
1064
1056
1065
/* *
@@ -1079,7 +1088,7 @@ public open class JobSupport(active: Boolean) : Job, SelectClause0, SelectClause
1079
1088
1080
1089
// for nicer debugging
1081
1090
override final fun toString (): String =
1082
- " ${nameString()} {${stateString()} }@${ Integer .toHexString( System .identityHashCode( this ))} "
1091
+ " ${nameString()} {${stateString()} }@$hexAddress "
1083
1092
1084
1093
/* *
1085
1094
* @suppress **This is unstable API and it is subject to change.**
@@ -1393,22 +1402,22 @@ private class InvokeOnCancellation(
1393
1402
1394
1403
internal class Child (
1395
1404
parent : JobSupport ,
1396
- val child : Job
1405
+ @JvmField val childJob : Job
1397
1406
) : JobCancellationNode<JobSupport>(parent) {
1398
1407
override fun invoke (reason : Throwable ? ) {
1399
1408
// Always materialize the actual instance of parent's completion exception and cancel child with it
1400
- child .cancel(job.getCancellationException())
1409
+ childJob .cancel(job.getCancellationException())
1401
1410
}
1402
- override fun toString (): String = " Child[$child ]"
1411
+ override fun toString (): String = " Child[$childJob ]"
1403
1412
}
1404
1413
1405
1414
private class ChildCompletion (
1406
1415
private val parent : JobSupport ,
1407
- private val waitChild : Child ,
1416
+ private val child : Child ,
1408
1417
private val proposedUpdate : Any?
1409
- ) : JobNode<Job>(waitChild. child) {
1418
+ ) : JobNode<Job>(child.childJob ) {
1410
1419
override fun invoke (reason : Throwable ? ) {
1411
- parent.continueCompleting(waitChild , proposedUpdate)
1420
+ parent.continueCompleting(child , proposedUpdate)
1412
1421
}
1413
1422
}
1414
1423
0 commit comments