Skip to content

fix: give SIGINT and SIGTERM back to Python after the JVM starts - #26

Merged
tae898 merged 1 commit into
mainfrom
fix/ctrl-c-sigterm
Oct 24, 2026
Merged

tae898 merged 1 commit into
mainfrom
fix/ctrl-c-sigterm

Conversation

@tae898

@tae898 tae898 commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

Ctrl-C does not stop a script that is blocked in one long call in the main thread on the JVM wheel, and kill -TERM aborts the process with status -6. This ports the fix of humemai/arcadedb-embedded-python#494 (merged as a9ed1284cf) to this repo. It is the Ctrl-C item of the Python runtime fixes group in #19.

Why

With the default interrupt=False, jpype.startJVM installs sun.misc.Signal handlers for SIGINT and SIGTERM, which replace Python's handlers at the operating-system level. A SIGINT goes to the JVM, whose signal-dispatcher thread tells Python later (PyErr_SetInterruptEx). That sets the "interrupt pending" flag but wakes nobody: the main thread got EINTR before the flag was set, went back into time.sleep(), and the KeyboardInterrupt waited for the end of the call. With no Python handler for SIGTERM the same path crashes the process (jpype-project/jpype#1497). Data was never affected. The native wheel has no JPype and no Java handler, so it was never affected.

What

start_jvm() calls signal.signal(sig, signal.getsignal(sig)) for SIGINT and SIGTERM right after the JVM starts, when interrupt is not True (main thread only, skipped elsewhere). That puts the operating-system disposition back, so both signals behave as in a script without a JVM. A Java call in progress is still not interrupted by Ctrl-C (the KeyboardInterrupt arrives when it returns); kill -TERM ends the process with -15 and runs no Python cleanup (committed data is intact, the next open replays the log). interrupt=True is unchanged. The start_jvm docstring is updated; this repo has no user docs pages yet (the Antora component is still open in #23), so there is no known-issues or API page to change.

Before and after

Numbers from #494 (wheel 26.10.2.post1 environment, child with a transaction holding one insert, SIGINT 0.3 s after the transaction opened, bound 10 s):

Case Before After
time.sleep(30) in the main thread, transaction open, SIGINT hung 20 of 20 hung 0 of 20, exit 0, KeyboardInterrupt caught
busy Python loop, transaction open, SIGINT hung 20 of 20 (never raised) hung 0 of 20, exit 0
SIGTERM, transaction open exit -6 in 10 of 10 exit -15 in 10 of 10

Tests

tests/test_sigint.py gets test_ctrl_c_wakes_a_long_blocking_call_with_a_transaction_open (a single time.sleep(60) inside a transaction, SIGINT, must exit within 20 s with KeyboardInterrupt, rollback, finally, atexit) and test_sigterm_ends_the_process_with_status_minus_15. The bounds are hang detectors, not latency bounds.

Run locally with uv run against the 26.11.1.dev0 wheel of humemai/arcadedb-embedded-python overlaid with this branch's src/arcadedb_embedded/*.py (no wheel was built from this tree): test_sigint.py, test_jvm.py, test_jvm_args.py, 22 passed. On the unmodified code the two new tests fail (child still running 20 s after SIGINT; exit -6 on SIGTERM) and the other 20 pass. black, isort and bandit are clean. CI builds the real wheel.

Refs #19, humemai/arcadedb-embedded-python#494.

🤖 Generated with Claude Code

JPype's default startJVM(interrupt=False) installs JVM handlers for SIGINT and SIGTERM. Ctrl-C then only set a pending flag and never woke a main thread blocked in time.sleep, Lock.acquire or queue.get, and SIGTERM aborted with status -6 (jpype-project/jpype#1497). start_jvm() now re-installs Python's handlers right after the JVM starts, on the main thread, unless interrupt=True.

Ported from humemai/arcadedb-embedded-python#494. Refs #19.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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