Restore the park state when a parked thunk unwinds - #1844
Open
dg1sbg wants to merge 1 commit into
Open
Conversation
CALL-PARKED and CALL-UNPARKED assigned the old state to a local, called the thunk, and restored the state afterwards. A thunk that exits non-locally skipped the restore, leaving the thread running Lisp while still marked GC-safe and blocking: racing the collector, and lying to PROCESS-KILL about its state forever. Cancelling a thread blocked in a syscall is exactly a non-local exit out of a parked region, so this is on the normal path rather than an edge case, and it becomes commonplace once accept, read and select park. Both now restore via a destructor, so any exit path -- normal return, C++ exception, or a Lisp non-local exit -- puts the thread back. Note the assumption: unwinding must run destructors, which holds for clasp's ordinary exits but not for the SJLJ paths used on macOS arm64 for save-lisp-and-die. Pre-existing; found while making blocking calls cancellable.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
gctools::call_parkedsaves the old park state, calls the thunk, then restores it:A thunk that exits non-locally skips the restore. The thread then resumes running Lisp while still marked GC-safe and blocking — racing the collector, and reporting
blockingp()forever after, which is whatProcess_O::interruptuses to decide whether topthread_kill(SIGCONT).call_unparkedhas the mirror-image flaw.This is not an edge case. Cancelling a thread blocked in a syscall is precisely a non-local exit out of a parked region, and
handle_SIGCONTis built to do exactly that — it callshandle_queued_interrupts()from inside the handler, and its own comment ("Nothing jumped out of there, so we're back to blocking") anticipates that something can jump out.It is reachable today: a thread cancelled while in
safe_open,clasp_musleep, orConditionVariable::wait/timed_waittakes this path.Both now restore through a destructor, so any exit — normal return, C++ exception, or a Lisp non-local exit — puts the thread back.
Caveat worth stating
This assumes unwinding runs destructors. That holds for ordinary Lisp non-local exits, but not for the SJLJ routing used on macOS arm64 (where
_longjmphas no unwind tables). Alongjmppast a park would still skip the restore. Fixing that properly needs the park state restored by the unwinder itself rather than by a C++ destructor; this change is a strict improvement over the status quo, not a complete guarantee.How it was found
While making blocking syscalls cancellable (#1843). Once
accept,readandselectpark, cancelling them exercises this path constantly rather than rarely. It is independent of that work, applies tomainas-is, and is worth taking on its own.No behaviour change on the normal path: the guard compiles to the same two calls in the same places.