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 following "unlucky execution" could happen:
Deferred in state RUNNING.
Thread A (in runCallbacks) | Thread B (in addCallbacks)
complete execution of the last callback | CAS state DONE -> RUNNING fails
acquire this' monitor |
observe that the callback chain is empty |
set state to DONE |
null out callback chains |
release this' monitor |
| acquire this' monitor
| queue the new callbacks
| release this' monitor
Now Thread B left the Deferred in state DONE with a callback in the
chain that will never be called unless another callback is added to it.
The fix consists in swapping the CAS and the acquisition of this'
monitor in addCallbacks. In the unlucky timing above, this could
have 2 possible outcomes:
1. Thread A acquires this' monitor first and then thread B's CAS
will succeed, so B will keep executing the callback chain.
2. Thread B acquires this' monitor first and queues the new
callbacks, then thread A will execute them.
When swapping the CAS and the acquisition of this' monitor, the CAS
becomes unnecessary and can be replaced with a regular volatile-read
followed by a regular volatile-write. The purpose of the CAS was
originally to avoid acquiring this' monitor when it wasn't necessary
but this "optimization" wasn't even effective since the most common
code path is to add a callback to a Deferred in state PENDING, and
this code path always needed a failed CAS + monitor acquisition.
Change-Id: I365cec225e9d15aa09280ce066bf11ce56c1d358
0 commit comments