Repository navigation
Pass a stop signal that another thread received on to the main thread while a job blocks the loop - #20
Merged
Conversation
…im still on its way to the worker at the stop is not handed back to the queue
Piergiuseppe
force-pushed
the
fix/stop-signals-reach-the-main-thread
branch
from
November 15, 2026 13:26
74c8475 to
b91ec51
Compare
… while a job blocks the loop, so a second Ctrl-C or SIGTERM forces the stop wherever each one landed instead of waiting for the job to return
…ork gate for a moment, so a write still finishing after the interrupt is not taken for a leak
Piergiuseppe
force-pushed
the
fix/stop-signals-reach-the-main-thread
branch
from
November 15, 2026 14:05
b91ec51 to
0b161d5
Compare
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.
What was wrong
A stop signal sent to the process is delivered by the kernel to any one thread. Python runs its handlers only on the main thread, and a signal another thread received does not interrupt a blocking call the main thread is in. With a job blocking the worker's loop, a
Ctrl-CorSIGTERMthat landed on a helper thread waited for the job to return, and two presses of the same signal that waited together ran the handler once, so the second press did not force the stop. This is the rare CI failure "the worker ignored a second Ctrl-C (own, blocking)": about 1 in 1,200 to 1,700 forced stops on Linux.What changes
Python writes the number of every signal the process receives, on whichever thread, to the loop's wakeup channel. While a run's handlers are in place a thread watches that channel without reading it. A stop signal found unread twice, 50 ms apart, means the loop is blocked, and the thread sends the main thread one
SIGURG, whose handler is empty: the interrupted system call makes Python run the pending stop handler, which is what happens when the kernel picks the main thread in the first place.SIGURGis taken only when it is unhandled in Python and at thesigactionlevel, and is put back afterwards, in a forked child too.handle_signalsgives Python its loop's wakeup channel while it lasts, if none is set.Two designs were tried and dropped on this branch before this one: blocking the stop signals in tinyjobs' threads (masks are inherited by user threads and child processes), and periodic nudges while a stopping loop did not answer (the probe below still hung, because the common case is the first press landing elsewhere).
Measured
DROP TABLEdeadlock that Give a process room to exit in the tests that time one #19 fixed, now on main.Also here
test_a_stopped_worker_waits_for_a_def_job_on_an_extra_loopstopped its worker when the store counted two running jobs, not when both had begun, so a claim still on its way was handed back. It now waits for both to begin.Docs
docs/design.md(stop-signal section),docs/operating-it.md,CHANGELOG.md(0.19.0, Fixed).