lua: run __shutdown__ with every Lua state quiesced - #13556
Conversation
c5d29ad to
aa043d4
Compare
|
I think there is still a remap-state re-entry window at The lifecycle hook returns before My suggested fix is to add a per-state execution-stop flag, for example
Because the flag is set only while the barrier owns all state mutexes and is read only after acquiring the corresponding mutex, it does not need to be atomic: callbacks that were already executing drain before the barrier completes, while callbacks queued behind it see the stopped state. I would also extend the AuTest with a remap Lua callback held/queued across SIGTERM and assert that it never logs execution after the global shutdown marker. |
A __shutdown__ function usually releases process global resources, and often does so through FFI into a native library. The shutdown handler locked only the state whose __shutdown__ it was invoking and released that lock before moving to the next state, so the callback running in state 0 could tear those resources down while a request callback was still using them in state 1. Production has crashed this way during restart, inside a global read-request callback rather than in any shutdown path. This patch hands the callbacks to a thread of its own, which acquires every main Lua state mutex, global and remap, before invoking any __shutdown__ function and keeps them for the rest of the process lifetime, so nothing queued behind one of them can enter Lua after the resources it uses are gone. A separate thread is required because ProxyMutex is recursive per event thread: the lifecycle thread would still enter Lua itself when it returns to its event loop for a final iteration. Acquisition is bounded, and the callbacks are skipped with an error rather than run against a state that never went idle. Remap instance teardown declines to wait on the retained mutexes, so it cannot deadlock the rest of shutdown. A single continuation now invokes every script's __shutdown__, so the states are quiesced exactly once no matter how many scripts define one.
aa043d4 to
c20efbf
Compare
In draft until @shukitchan is available to review.
A shutdown function usually releases process global resources, and
often does so through FFI into a native library. The shutdown handler
locked only the state whose shutdown it was invoking and released
that lock before moving to the next state, so the callback running in
state 0 could tear those resources down while a request callback was
still using them in state 1. Production has crashed this way during
restart, inside a global read-request callback rather than in any
shutdown path.
This patch hands the callbacks to a thread of its own, which acquires
every main Lua state mutex, global and remap, before invoking any
shutdown function and keeps them for the rest of the process
lifetime, so nothing queued behind one of them can enter Lua after the
resources it uses are gone. A separate thread is required because
ProxyMutex is recursive per event thread: the lifecycle thread would
still enter Lua itself when it returns to its event loop for a final
iteration. Acquisition is bounded, and the callbacks are skipped with an
error rather than run against a state that never went idle. Remap
instance teardown declines to wait on the retained mutexes, so it cannot
deadlock the rest of shutdown. A single continuation now invokes every
script's shutdown, so the states are quiesced exactly once no matter
how many scripts define one.