Skip to content

Commit a3f5532

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 d31725c commit a3f5532

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
@@ -939,11 +939,11 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
939939
// process cancelling notification here -- it cancels all the children _before_ we start to wait them (sic!!!)
940940
notifyRootCause?.let { notifyCancelling(list, it) }
941941
// now wait for children
942-
val child = firstChild(state)
942+
val child = list.nextChild()
943943
if (child != null && tryWaitForChild(finishing, child, proposedUpdate))
944944
return COMPLETING_WAITING_CHILDREN
945945
list.close(LIST_CHILD_PERMISSION)
946-
val anotherChild = firstChild(state)
946+
val anotherChild = list.nextChild()
947947
if (anotherChild != null && tryWaitForChild(finishing, anotherChild, proposedUpdate))
948948
return COMPLETING_WAITING_CHILDREN
949949
// otherwise -- we have not children left (all were already cancelled?)
@@ -953,9 +953,6 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
953953
private val Any?.exceptionOrNull: Throwable?
954954
get() = (this as? CompletedExceptionally)?.cause
955955

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

0 commit comments

Comments
 (0)