Fix interrupting. Add tests. (#62069)
An attempt at fixing interrupt handling, also some specific interrupt
hardening from manual tests, and adds interrupt tests.
Fixes #58689
Closes #58849
Developed with Claude Fable 5:
----
Since #57544, an idle thread parks in a per-thread internal scheduler
task. A SIGINT is always delivered to thread 1, which is almost always
parked when the signal arrives, so the resulting `InterruptException`
landed in the scheduler task's `wait_forever`, where it was reported as
a confusing `Internal Task ERROR: InterruptException` and then dropped.
As a result Ctrl-C no longer reached user code:
- scripts blocked in `sleep`/IO could not be interrupted at all,
- the REPL printed internal task errors on every Ctrl-C,
- `Distributed.interrupt` (which just sends SIGINT to the worker
process) became a silent no-op, breaking remote interrupts in
Distributed, Malt.jl/Pluto, and IJulia.
1.12 and 1.13 worked around this by reverting #57544 and its follow-ups;
master still had the scheduler task and the broken behavior.
## What this does
**`base/task.jl`** — Each thread now remembers the last user task that
yielded into the scheduler while going idle (never recording a completed
task, so this does not delay collection of done tasks — the problem
#57544 fixed). When an `InterruptException` is delivered to the
scheduler task, it is re-thrown into a task that can meaningfully
observe it instead of being dropped: the REPL backend if it is
evaluating user code; silently dropped at an idle REPL prompt; otherwise
the last idle task, falling back to the root task. Expected failures of
the redirect (the victim raced to be rescheduled, or a second interrupt
arrived mid-switch) drop the interrupt; anything unexpected is still
reported. Delivery remains best-effort, as it always has been; robust
cancellation is left to #60281.
**`src/gf.c`** — Interrupting a process that is compiling (e.g. Ctrl-C
during `Pkg.test`) frequently threw the `InterruptException` into type
inference via the safepoint, unwinding the compiler mid-flight
("`Internal error: during type inference of ...`", an abort in assertion
builds, and a lost interrupt). The inference entry point is now
signal-atomic, so the interrupt is deferred and rethrown once compiler
state is consistent. Forced interrupts (repeated Ctrl-C) bypass the
deferral as before.
**`stdlib/REPL/src/REPL.jl`** — An interrupt forwarded to the REPL
backend just as user code finished evaluating (the forwarder checks
`in_eval`, but eval can complete before the throw lands) was raised at
`take!(backend.repl_channel)` and tore down the whole REPL session. The
backend loop now ignores a stray `InterruptException` there and keeps
serving.
## Validation
New regression tests in `test/misc.jl` (pty-driven REPL + subprocess
scenarios, Unix-only) and `stdlib/REPL/test/repl.jl`, all derived from
the issue reports:
| Scenario | 1.11 | master before | master after |
|---|---|---|---|
| SIGINT at idle REPL prompt | ok | internal-error noise | ok |
| SIGINT during REPL `sleep` loop | ok | noise + interrupt works | ok |
| SIGINT to `julia -e 'sleep(600)'` | exits after 2nd SIGINT | never
exits | exits cleanly on 1st |
| `Distributed.interrupt` of a busy worker | `RemoteException` | silent
no-op | `RemoteException` |
| SIGINT during `Pkg.test`-style run (compiling) | — | inference
internal error / abort | clean `InterruptException` |
The fix is platform-independent (the Windows delivery path in
`signals-win.c` lands in the same scheduler task), but the tests are
Unix-only since sending a console Ctrl-C from the test harness on
Windows requires `GenerateConsoleCtrlEvent`/`CREATE_NEW_PROCESS_GROUP`,
which the spawn API doesn't expose.
This also adds the Unix portion of the CI coverage requested in #58849,
and likely fixes the crash class in #50045 (unverified, needs network).
Fixes #58689
Fixes #29369
Fixes #43451
Closes #58849
Fixes #50045
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>