Skip to content

Commit d989e06

Browse files
committed
Fix a newcoming child being sometimes ignored
The failure went like this: * A child arrives. * In the meantime, the parent enters `tryMakeCompletingSlowPath` and remembers the current list of handlers, which is an empty or a single-element one. * The parent updates the state to the finishing one. * The child enters the list. * The parent traverses *an old list*, the one from before the child arrived. It sees no children in the empty/single-element list and forgets about the child. Why, then, was it that this worked before? It was because there was a guarantee that no new children are going to be registered if three conditions are true: * The state of the `JobSupport` is a list, * The root cause of the error is set to something, * And the state is already "completing". `tryMakeCompletingSlowPath` sets the state to completing, and because it updates the state inside `synchronized`, there was a guarantee that the child would see either the old state (and, if it adds itself successfully, then `tryMakeCompletingSlowPath` will retry) or the complete new one, with `isCompleting` and the error set to something. So, there could be no case when a child entered a *list*, but this list was something different from what `tryMakeCompletingSlowPath` stores in its state.
1 parent 0c52fe8 commit d989e06

File tree

1 file changed

+2
-5
lines changed

1 file changed

+2
-5
lines changed

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

+2-5
Original file line numberDiff line numberDiff line change
@@ -929,11 +929,11 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
929929
// process cancelling notification here -- it cancels all the children _before_ we start to wait them (sic!!!)
930930
notifyRootCause?.let { notifyCancelling(list, it) }
931931
// now wait for children
932-
val child = firstChild(state)
932+
val child = list.nextChild()
933933
if (child != null && tryWaitForChild(finishing, child, proposedUpdate))
934934
return COMPLETING_WAITING_CHILDREN
935935
list.close(LIST_CHILD_PERMISSION)
936-
val anotherChild = firstChild(state)
936+
val anotherChild = list.nextChild()
937937
if (anotherChild != null && tryWaitForChild(finishing, anotherChild, proposedUpdate))
938938
return COMPLETING_WAITING_CHILDREN
939939
// otherwise -- we have not children left (all were already cancelled?)
@@ -943,9 +943,6 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
943943
private val Any?.exceptionOrNull: Throwable?
944944
get() = (this as? CompletedExceptionally)?.cause
945945

946-
private fun firstChild(state: Incomplete) =
947-
state as? ChildHandleNode ?: state.list?.nextChild()
948-
949946
// return false when there is no more incomplete children to wait
950947
// ## IMPORTANT INVARIANT: Only one thread can be concurrently invoking this method.
951948
private tailrec fun tryWaitForChild(state: Finishing, child: ChildHandleNode, proposedUpdate: Any?): Boolean {

0 commit comments

Comments
 (0)