Skip to content

WIP: cancellation: Hook up ^C and put everything together - #62648

Closed
Keno wants to merge 10 commits into
JuliaLang:masterfrom
KenoAIStaging:kf/cancellation-sigint
Closed

WIP: cancellation: Hook up ^C and put everything together#62648
Keno wants to merge 10 commits into
JuliaLang:masterfrom
KenoAIStaging:kf/cancellation-sigint

Conversation

@Keno

@Keno Keno commented Aug 6, 2026

Copy link
Copy Markdown
Member

Now that everything else is merged, this is the final extract from #60281 to actually turn everything on. As usual, WIP while I clean up after the AI.

@Keno
Keno requested review from d-netto, qinsoon and udesou as code owners August 6, 2026 01:24
@Keno

Keno commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

Some of this is separable - I'll split this up some more.

@Keno
Keno force-pushed the kf/cancellation-sigint branch 5 times, most recently from ecb661c to 5b77e7a Compare August 6, 2026 08:38
Keno and others added 10 commits August 6, 2026 17:43
Replace the old ^C delivery - throwing an InterruptException into whatever
the root task was doing at a GC safepoint - with delivery through the
cancellation system: SIGINT marks the ^C episode source cancelled directly
from the signal path (async-signal-safely, so a single-threaded process
stuck in compute still observes it) and notifies a Julia-side listener
task that performs the remaining delivery work (waking parked waiters).
The per-thread cancellation sends also propagate a pending ^C episode into
the interrupted task's own bound descendant source, so scoped computations
observe the press even when the listener is starved, and idle threads take
over a stalled event loop so the notification can always be dispatched.

The episode source itself is installed by Base around each foreground
evaluation (see the companion commit); `exit_on_sigint` mode is
unchanged.

This pull request was written with the assistance of generative AI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add the Julia half of the new ^C delivery: a sigint listener task per
nonempty threadpool claims each notification and completes the delivery
(cancelling a fresh episode source, or re-running the level-triggered
delivery pass for one the signal path already marked - `Base.redeliver!`
- so parked waiters get their wake), and interactive drivers install a
fresh ^C episode source per foreground evaluation - so a press cancels
exactly that evaluation's scope - closing it when the work item completes.
Episode sources are linked under the session source, keeping the work an
evaluation leaves behind sweepable by `Base.cancel_session_work!`.
`@sync`, `Threads.@threads` and `Experimental.@sync` blocks now
scope a cancellation source over their children, with teardown that keeps
awaiting internal tasks across severity escalations and re-arms its waits
at each rung.

This pull request was written with the assistance of generative AI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The sigint listener task started from `Base.__init__` is not statically
reachable in trimmed binaries; pin JuliaC to a staging branch whose trim
overlay stubs it out, until the stub lands upstream.

This pull request was written with the assistance of generative AI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 3595391802040e7a344d00d256c3e1567556b7fb)
Subprocess and pty scenarios for the new SIGINT semantics: catching and
re-arming in scripts, propagation through `@sync`/`Threads.@threads`/
`Experimental.@sync` scopes (including teardown that keeps awaiting
internal tasks across explicit severity escalations), the episode
re-arm/close cycle in the REPL, and the historical ^C crash classes
(issues JuliaLang#4037-adjacent delivery, JuliaLang#25790, JuliaLang#29369/JuliaLang#45055, JuliaLang#42072, JuliaLang#43451,
JuliaLang#47839, JuliaLang#50045, JuliaLang#56462, JuliaLang#56545, JuliaLang#58105, JuliaLang#58689, JuliaLang#49541's watcher shape).
Existing tests that asserted the legacy force-thrown InterruptException
contract are updated to the cancellation-request delivery.

This pull request was written with the assistance of generative AI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Give `CANCEL_REQUEST_ABANDON_ALL` its documented delivery semantics: tasks
that do not acknowledge the request are frozen, never to run again,
instead of being interrupted. A running victim is ripped from its thread
through the task-abandonment handshake; a parked or queued one is marked
frozen where it stands (its stale registrations discard on later
notifies); a task cancelled before it first ran completes into a new
`cancelled` state that a late `schedule` transitions to failed instead of
erroring. Spawning into a frozen scope is refused at task start - the
freeze contract is that nothing under the scope makes further progress,
and the spawned body's first cancellation point may lie arbitrarily far
in.

Sources additionally track which severities some task has *observed* (a
`delivered` bitmask maintained at cancellation points and walk
deliveries, async-signal-safe for C-side reads): the distinction between
a request in flight and one whose target never saw it is what the ^C
escalation ladder's rung selection is built on.

This pull request was written with the assistance of generative AI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Build the ^C escalation ladder on top of the cancellation-based SIGINT
delivery: every press arms a one-second rescue timer; if the process does
not acknowledge the cancellation in time, the timer prints a
rung-appropriate offer, and the next press climbs one rung
(SAFE -> ABANDON_EXTERNAL -> ABANDON_ALL), with a C-side direct
abandonment (polled, never waited for on the signal listener) as the
fallback when no thread is available to run the julia-side listener at
all. ^T/SIGINFO (or SIGUSR1) prints thread backtraces during an episode.

This pull request was written with the assistance of generative AI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extend the sigint listener with the escalation state machine: retry
undelivered requests, escalate one severity rung after the rescue timer's
offer, freeze a foreground task without a published token binding at the
final rung, and clean up a forcibly abandoned target - rescuing the REPL
backend (or exiting the session when nothing can resume). The rescue task
gives a thread whose current task was abandoned something runnable to
switch to, and process exit disarms the rescue timer.

The listener pass touches REPL code only after a target was actually
frozen: invoking (and possibly compiling) it earlier can deadlock the
compile's GC against a cancelled victim that is still spinning without
safepoints - precisely the victim the remaining rungs must cut through.

This pull request was written with the assistance of generative AI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each REPL work item runs under a fresh ^C episode token, closed when
control returns to the prompt, so a ^C cancels exactly the current
evaluation and an idle press is a no-op; result reporting is shielded so a
cancelled epoch cannot lose the response. When the escalation ladder had to
forcibly abandon the backend task, inform the frontend and restart the
backend so the session survives. The rescue path is precompiled into the
pkgimage: the sigint listener invokes it during a ^C episode, where a
first-time compile could deadlock against an unresponsive victim.

This pull request was written with the assistance of generative AI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Point Distributed at a staging branch where a worker-side `interrupt`
cancels the remotely-submitted work's cancellation scope instead of
throwing an InterruptException into an arbitrary task; retarget to
JuliaLang/Distributed.jl once merged upstream.

This pull request was written with the assistance of generative AI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ladder scenarios on top of the core ^C delivery tests: the rescue timer's
escalation offers and rung climbing (subprocess and pty, at several thread
counts), direct abandonment of unresponsive compute, the
catch-all-swallowing loop that cannot hide from ^C, REPL backend rescue
after an abandonment, and the threaded cancellation exec suite.

This pull request was written with the assistance of generative AI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Keno
Keno force-pushed the kf/cancellation-sigint branch from 5b77e7a to bd3b277 Compare August 6, 2026 20:05
@Keno Keno mentioned this pull request Aug 6, 2026
@Keno

Keno commented Aug 8, 2026

Copy link
Copy Markdown
Member Author

Closing in favor of #62663, which is the last piece.

@Keno Keno closed this Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant