Skip to content

Commit 1cc88d6

Browse files
committed
Simplify adding the children to just two operations on a list
1 parent 023b194 commit 1cc88d6

File tree

1 file changed

+33
-41
lines changed

1 file changed

+33
-41
lines changed

kotlinx-coroutines-core/common/src/JobSupport.kt

+33-41
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
889889
if (finishing.isCompleting) return COMPLETING_ALREADY
890890
// mark as completing
891891
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.
893893
// We do it as early is possible while still holding the lock. This ensures that we cancelImpl asap
894894
// (if somebody else is faster) and we synchronize all the threads on this finishing lock asap.
895895
if (finishing !== state) {
@@ -903,7 +903,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
903903
// If it just becomes cancelling --> must process cancelling notifications
904904
notifyRootCause = finishing.rootCause.takeIf { !wasCancelling }
905905
}
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!!!)
907907
notifyRootCause?.let { notifyCancelling(list, it) }
908908
// now wait for children
909909
val child = firstChild(state)
@@ -986,47 +986,39 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
986986
* but the parent *will* wait for that child before completion and will handle its exception.
987987
*/
988988
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
1013995
} 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
10271014
}
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
10301022
}
10311023
}
10321024
}

0 commit comments

Comments
 (0)