Share interrupt state across all SCS extensions (fixes swallowed SIGINT) - #231
Merged
Conversation
bodono
force-pushed
the
fix/shared-interrupt
branch
7 times, most recently
from
August 31, 2026 11:01
3ce285d to
7769fcd
Compare
bodono
added a commit
that referenced
this pull request
Aug 31, 2026
- legacy_setup.py rejects --blas64 outright: the legacy build cannot verify an ILP64 BLAS provider (even --mkl may resolve an LP64 mkl_rt), and 64-bit prototypes against LP64 BLAS corrupt memory. The meson build selects and verifies the ILP64 interface; point there. - legacy_setup.py no longer references scs/py_ctrlc.c, which this branch does not contain -- that hunk moves to #231 where the file is introduced. - The pinned-negative regularization test now requires construction plus a solved status; a rejection by the pinned core skips loudly (TODO: becomes a hard failure at the post-#421 re-pin) and a rejection by the wrapper itself still fails. - The ILP64 guard step asserts the core's specific 'MKL interface layer mismatch' diagnostic instead of accepting any ValueError, and runs under set -euo pipefail (bash -l drops GitHub's errexit).
bodono
force-pushed
the
fix/shared-interrupt
branch
4 times, most recently
from
August 31, 2026 19:51
bced0a3 to
0f4d214
Compare
bodono
added a commit
that referenced
this pull request
Aug 31, 2026
- legacy_setup.py rejects --blas64 outright: the legacy build cannot verify an ILP64 BLAS provider (even --mkl may resolve an LP64 mkl_rt), and 64-bit prototypes against LP64 BLAS corrupt memory. The meson build selects and verifies the ILP64 interface; point there. - legacy_setup.py no longer references scs/py_ctrlc.c, which this branch does not contain -- that hunk moves to #231 where the file is introduced. - The pinned-negative regularization test now requires construction plus a solved status; a rejection by the pinned core skips loudly (TODO: becomes a hard failure at the post-#421 re-pin) and a rejection by the wrapper itself still fails. - The ILP64 guard step asserts the core's specific 'MKL interface layer mismatch' diagnostic instead of accepting any ValueError, and runs under set -euo pipefail (bash -l drops GitHub's errexit).
bodono
force-pushed
the
fix/review-batch
branch
from
August 31, 2026 22:19
b6f7bc6 to
f5028a1
Compare
bodono
force-pushed
the
fix/shared-interrupt
branch
2 times, most recently
from
September 1, 2026 07:36
26cf495 to
ce901bb
Compare
bodono
force-pushed
the
fix/review-batch
branch
2 times, most recently
from
September 3, 2026 19:53
73d35e1 to
1b416a2
Compare
bodono
force-pushed
the
fix/shared-interrupt
branch
2 times, most recently
from
September 3, 2026 19:53
62730e2 to
ae8b57a
Compare
bodono
force-pushed
the
fix/review-batch
branch
from
September 3, 2026 20:10
1b416a2 to
2ca97c6
Compare
bodono
force-pushed
the
fix/shared-interrupt
branch
from
September 3, 2026 20:10
ae8b57a to
d22c26c
Compare
bodono
force-pushed
the
fix/review-batch
branch
from
September 3, 2026 21:07
2ca97c6 to
90e7010
Compare
bodono
force-pushed
the
fix/shared-interrupt
branch
from
September 3, 2026 21:07
d22c26c to
d074a0f
Compare
bodono
force-pushed
the
fix/review-batch
branch
from
September 3, 2026 21:17
90e7010 to
93f0f1f
Compare
bodono
force-pushed
the
fix/shared-interrupt
branch
from
September 3, 2026 21:17
d074a0f to
951b8d0
Compare
bodono
force-pushed
the
fix/review-batch
branch
from
September 3, 2026 21:29
93f0f1f to
195d884
Compare
bodono
force-pushed
the
fix/shared-interrupt
branch
from
September 3, 2026 21:29
951b8d0 to
228d2f0
Compare
bodono
force-pushed
the
fix/review-batch
branch
from
September 4, 2026 11:36
195d884 to
aad08cc
Compare
bodono
force-pushed
the
fix/shared-interrupt
branch
from
September 4, 2026 11:37
228d2f0 to
dbb2f75
Compare
bodono
force-pushed
the
fix/review-batch
branch
from
September 4, 2026 16:49
aad08cc to
cb01c12
Compare
bodono
force-pushed
the
fix/shared-interrupt
branch
from
September 4, 2026 16:49
dbb2f75 to
4540245
Compare
Every extension compiles its own copy of the solver, so file-static interrupt state gave each one a private saved handler and refcount. Overlapping solves from two different extensions then saved and restored the SIGINT handler non-LIFO and left a dead SCS handler installed after both finished: SIGINT was swallowed for the rest of the process. Python builds now compile scs/py_ctrlc.c instead of the upstream ctrlc.c: one state struct per process, published as a PyCapsule in a registry module's dict with PyDict_SetDefault so racing imports agree on it, and resolved once by each extension at import time. The interrupt flag is a C11 atomic (signal- and thread-safe); the solver-facing functions touch no Python API. Module init releases the module on every failure exit. The test runs the real scenario in a subprocess with barrier-synchronized threads: two overlapping solves from different backends are both interrupted by one SIGINT, and Python's handler is back afterwards. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
bodono
force-pushed
the
fix/shared-interrupt
branch
from
September 5, 2026 13:59
4540245 to
debf896
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.
Fixes the highest-severity scs-python finding from the 3.3.0 review, reproduced exactly as described: after overlapping QDLDL and CPU_INDIRECT solves, a self-sent SIGINT was permanently swallowed.
Mechanism: each extension compiles its own
ctrlc.c, so saved-handler/refcount state is per-extension. Non-LIFO overlap makes extension B save A's transient handler as "previous" and restore it last — a dead SCS handler stays installed forever.Fix: Python builds swap
scs_source/src/ctrlc.cforscs/py_ctrlc.c(same three-function interface, vendored source untouched) operating on one process-shared state struct, published as a PyCapsule via atomicPyDict_SetDefaultinsert-if-absent (race-safe under free-threading), resolved once per extension at import with the GIL held. One shared refcount ⟹ the real previous handler is saved once and restored once, however solves interleave. POSIX + Windows.Test (subprocess-based, POSIX): one barrier-synchronised reproduction overlaps a QDLDL and a CPU_INDIRECT solve, sends SIGINT during the overlap, and checks that both solves stop and the original handler is back afterwards; before the fix the handler was swallowed.
Stacked on #230 (shares meson.build).
🤖 Generated with Claude Code