@@ -889,7 +889,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
889
889
if (finishing.isCompleting) return COMPLETING_ALREADY
890
890
// mark as completing
891
891
finishing.isCompleting = true
892
- // if we need to promote to finishing then atomically do it here.
892
+ // if we need to promote to finishing, then atomically do it here.
893
893
// We do it as early is possible while still holding the lock. This ensures that we cancelImpl asap
894
894
// (if somebody else is faster) and we synchronize all the threads on this finishing lock asap.
895
895
if (finishing != = state) {
@@ -903,7 +903,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
903
903
// If it just becomes cancelling --> must process cancelling notifications
904
904
notifyRootCause = finishing.rootCause.takeIf { ! wasCancelling }
905
905
}
906
- // process cancelling notification here -- it cancels all the children _before_ we start to to wait them (sic!!!)
906
+ // process cancelling notification here -- it cancels all the children _before_ we start to wait them (sic!!!)
907
907
notifyRootCause?.let { notifyCancelling(list, it) }
908
908
// now wait for children
909
909
val child = firstChild(state)
@@ -986,47 +986,39 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
986
986
* but the parent *will* wait for that child before completion and will handle its exception.
987
987
*/
988
988
val node = ChildHandleNode (child).also { it.job = this }
989
- val added = tryPutNodeIntoList(node) { state, list ->
990
- if (state is Finishing ) {
991
- val rootCause: Throwable ?
992
- val handle: ChildHandle
993
- synchronized(state) {
994
- // check if we are installing cancellation handler on job that is being cancelled
995
- rootCause = state.rootCause // != null if cancelling job
996
- // We add the node to the list in two cases --- either the job is not being cancelled,
997
- // or we are adding a child to a coroutine that is not completing yet
998
- if (rootCause == null || ! state.isCompleting) {
999
- // Note: add node the list while holding lock on state (make sure it cannot change)
1000
- handle = if (list.addLast(node, LIST_CHILD_PERMISSION )) {
1001
- node
1002
- } else {
1003
- NonDisposableHandle
1004
- }
1005
- // just return the node if we don't have to invoke the handler (not cancelling yet)
1006
- // otherwise handler is invoked immediately out of the synchronized section & handle returned
1007
- } else {
1008
- handle = NonDisposableHandle
1009
- }
1010
- }
1011
- node.invoke(rootCause)
1012
- return handle
989
+ val added = tryPutNodeIntoList(node) { _, list ->
990
+ // First, try to add a child along the cancellation handlers
991
+ val addedBeforeCancellation = list.addLast(node, LIST_CANCELLATION_PERMISSION )
992
+ if (addedBeforeCancellation) {
993
+ // The child managed to be added before the parent started to cancel or complete. Success.
994
+ true
1013
995
} else {
1014
- list.addLast(node, LIST_CHILD_PERMISSION ).also { success ->
1015
- if (success) {
1016
- /* * Handling the following case:
1017
- * - A child requested to be added to the list;
1018
- * - We checked the state and saw that it wasn't `Finishing`;
1019
- * - Then, the job got cancelled and notified everyone about it;
1020
- * - Only then did we add the child to the list
1021
- * - and ended up here.
1022
- */
1023
- val latestState = this @JobSupport.state
1024
- if (latestState is Finishing ) {
1025
- synchronized(latestState) { latestState.rootCause }?.let { node.invoke(it) }
1026
- }
996
+ // Either cancellation or completion already happened, the child was not added.
997
+ // Now we need to try adding it for completion.
998
+ val addedBeforeCompletion = list.addLast(node, LIST_CHILD_PERMISSION )
999
+ // Whether or not we managed to add the child before the parent completed, we need to investigate:
1000
+ // why didn't we manage to add it before cancellation?
1001
+ // If it's because cancellation happened in the meantime, we need to notify the child.
1002
+ // We check the latest state because the original state with which we started may not have had
1003
+ // the information about the cancellation yet.
1004
+ val rootCause = when (val latestState = this .state) {
1005
+ is Finishing -> {
1006
+ // The state is still incomplete, so we need to notify the child about the completion cause.
1007
+ synchronized(latestState) { latestState.rootCause }
1008
+ }
1009
+ else -> {
1010
+ // Since the list is already closed for `onCancelling`, the job is either Finishing or
1011
+ // already completed. We need to notify the child about the completion cause.
1012
+ assert { latestState !is Incomplete }
1013
+ (latestState as ? CompletedExceptionally )?.cause
1027
1014
}
1028
- // if we didn't add the node to the list, we'll loop and notice
1029
- // either `Finishing` or the final state, so no spin loop here
1015
+ }
1016
+ if (addedBeforeCompletion) {
1017
+ if (rootCause != null ) node.invoke(rootCause)
1018
+ true
1019
+ } else {
1020
+ node.invoke(rootCause)
1021
+ return NonDisposableHandle
1030
1022
}
1031
1023
}
1032
1024
}
0 commit comments