Repository navigation
Let a SIGINT leave the reaper task and restart it on the next connect - #255
Merged
Merged
Conversation
Track the reaper task in REAPER_TASK so a test can reach it. The new testset throws an InterruptException into the reaper while it waits on its timer, the way a Ctrl-C does when the reaper is the task resident on thread 1, and asserts that the exception leaves the task and that the next ensure_reaper! call starts a reaper that still reaps. Before the fix the testset fails: the catch around wait(t) swallows the InterruptException, the task ends normally, and nothing restarts it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nnect Julia throws a SIGINT's InterruptException into whichever task is resident on thread 1. The reaper wakes every 0.5 s and parks again, so during a long statement it is usually that task. Its catch around wait(t) took the InterruptException, ended the loop, and left the timer open, so the Ctrl-C never reached the user's task and nothing reaped again until exit. Remove the catch: the exception now leaves the task, which is what Base's task_done_hook needs to hand it to the REPL backend. Track the reaper task and let ensure_reaper! start a new one when the old one is done. Place the reaper on the default pool, so that when an interactive pool exists it never parks on thread 1 while the user's work runs there. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The parked check compared `reaper.queue` with the timer's wait queue, which no longer holds on Julia 1.15-dev: a parked task registers a WaitEntry there and `queue` stays `nothing`, so the nightly jobs never injected the interrupt. Check membership in the timer's wait queue instead, under its lock, reading the entry's atomic `task` field on 1.15 and the entry itself before that. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…Entry records Julia 1.13 keeps tasks in a Timer's wait queue; 1.14.0-alpha1 and later keep WaitEntry records. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged
quinnj
referenced
this pull request
Nov 5, 2026
Co-authored-by: Jacob Quinn <quinn.jacobd@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 5, 2026
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.
Why
Once MySQL.jl has opened a connection, a Ctrl-C during a long statement can vanish, and reaping stops afterwards (#254). Julia throws a SIGINT's
InterruptExceptioninto whichever task is resident on thread 1. The reaper wakes every 0.5 s and parks again, so during a long statement it is usually that task. Its catch aroundwait(t)took the exception, ended the loop, and left the timer open.What changed
reaper_loopcatches nothing aroundwait(t). AnInterruptExceptionnow leaves the task, which is what Base'stask_done_hookneeds to hand it to the REPL backend.ensure_reaper!tracks the task inREAPER_TASKand starts a new one on the next connect when the old one is done.-t N,M, or-t autoon 1.12) it never parks on thread 1 while the user's work runs there.Scope
Timer(cb)task and the Profile SIGINFO listener behave the same way.atexithook closes them.Tradeoffs
Threads.@spawn :defaultis the public way to pick a pool, but its closure thunk is not reachable from the static call edge--trimneeds.Task(reaper_loop)plusThreads._spawn_set_thrpoolkeeps the trim story as it is.Blast Radius
The reaper's task lifecycle only. The CAS states, exactly-once close,
reap_now!, and theatexithook are untouched. The reaper now runs on default threads when an interactive pool exists; transport close was already reached from any thread.Verification
-t 2,1, 3 of 3 runs lost the interrupt and left the reaper done with its timer open. After, 2 of 3 runs deliveredInterruptExceptionto the main task after 1.1 s, and the reaper kept reaping in all 3; the third run lost the interrupt inside the runtime, the same 1-in-3 drop a MySQL-free control shows on this Mac. With-t 1, before: swallowed and reaping stopped; after: the interrupt ends the reaper task andensure_reaper!()restarts it.test/protocol/native_tests.jlfails on main (5 of 8 assertions: the task ends normally, nothing restarts it, a queued entry is never closed) and passes with the fix on-t 1and on-t auto,2, on 1.12.6 and 1.10.11. The test finds the parked reaper through the timer's wait queue under its lock, which holds tasks before Julia 1.15 andWaitEntryrecords on 1.15-dev (the first CI round failed only the nightly jobs, on that check).MYSQL_INTEGRATION=0 Pkg.test()), macOS arm64: Julia 1.12.6 passed 1823 of 1823 (the--trim=safelane compiled and ran the workload, which exercises the reaper), Julia 1.10.11 passed 1816 of 1816.Review notes
Checked as a reviewer:
ensure_reaper!has one caller (register!inconnect.jl);wait(::Timer)throws onlyEOFErrorfor a closed timer, which nothing closes, or an exception thrown into the task; the restart runs underREAPER_SETUP_LOCK, so two concurrent connects start one task;atexitis still registered once; the test holds the timer's lock between the parked check and the throw, soschedule(..., error=true)never reaches a running task;_spawn_set_thrpoolhas the same signature on 1.10, 1.12, and master.Fixes #254
🤖 Generated with Claude Code