Skip to content

fix: stop searches gracefully on Windows console Ctrl-C - #1386

Merged
MilesCranmer merged 3 commits into
astroautomata:masterfrom
MilesCranmerBot:windows-console-ctrlc
Sep 27, 2026
Merged

MilesCranmer merged 3 commits into
astroautomata:masterfrom
MilesCranmerBot:windows-console-ctrlc

Conversation

@MilesCranmerBot

Copy link
Copy Markdown
Contributor

Pressing Ctrl-C in a Windows terminal during model.fit kills the Python process. #1329 made interrupts stop the search gracefully from Jupyter on Windows, but a console Ctrl-C never reaches Python.

Julia registers a console control handler at startup (SetConsoleCtrlHandler(sigint_handler, 1) in src/signals-win.c). Windows calls console handlers last-registered first, so Julia's runs before CPython's, returns TRUE, and exits the process with code 130. Python's SIGINT handler, and with it the stop byte that ExternalStop polls for, never fires.

For the duration of the search, PySR now registers its own console handler. Because it is registered last, it runs first. It passes CTRL_C_EVENT to Python through _thread.interrupt_main(), the same route Jupyter's interrupt already takes, and it leaves other console events to the existing handlers. This does for Windows what the sigaction save and restore already does for POSIX, so interrupt.py has one if/else covering both platforms.

TestSubprocessInterrupt now runs on Windows. The child process interrupts itself. On POSIX it sends os.kill(os.getpid(), SIGINT). On Windows, where it is launched with CREATE_NEW_CONSOLE, it calls GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0). The child runs a warm-up fit first so the interrupt lands on a running search, and it now requires non-empty partial results.

Verification, python -m pysr test interrupt:

run result
windows-latest, test change only fails: AssertionError: 130 != 0 : child failed
windows-latest, with fix Ran 11 tests, OK (skipped=5); the subprocess test ran and passed, and the five skips are the POSIX-only sigaction unit tests
ubuntu-latest, with fix Ran 11 tests, OK, no skips
macOS local, Python 3.12, Julia 1.11 Ran 11 tests, OK

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
@codecov

codecov Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 22.72727% with 17 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pysr/interrupt.py 22.72% 17 Missing ⚠️

📢 Thoughts on this report? Let us know!

MilesCranmerBot and others added 2 commits September 27, 2026 14:35
A Ctrl-C arriving while the original SIGINT handler is installed raises
KeyboardInterrupt, which could skip registering or removing the console
handler and leave Windows holding a freed callback. The test now sends
its interrupt once the fit has armed the search, instead of after a fixed
delay.

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
@MilesCranmer
MilesCranmer enabled auto-merge (squash) September 27, 2026 14:11
@MilesCranmer
MilesCranmer merged commit da64e04 into astroautomata:master Sep 27, 2026
48 checks passed
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.

2 participants