You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 two conditions are true:
* 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.
Additionally, `tryMakeCompletingSlowPath` is entered for empty or
single-element lists *almost only* when it's completing with an
error, so in effect, it was *almost always* guaranteed that new
children weren't going to be registered.
An exception is a tiny bug that used to be present, but wasn't
noticed by anyone because of how incredibly specific it was:
`tryMakeCompletingSlowPath` can also be entered for single-element
lists when completing without an error if that single element is
not a child. In this case, in extremely rare racy scenarios,
attaching a child could result in the child registering itself,
but the parent forgetting to wait for it.
0 commit comments