Skip to content

Commit 85c45a0

Browse files
committed
Simplify adding the children to just two operations on a list
1 parent ef43ce5 commit 85c45a0

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

0 commit comments

Comments
 (0)