Visitar URL original
Bindings: give SIGINT and SIGTERM back to Python after the JVM starts (long blocking call ignored Ctrl-C, SIGTERM exited -6) by tae898 · Pull Request #494 · humemai/arcadedb-embedded-python · GitHub
Skip to content

Bindings: give SIGINT and SIGTERM back to Python after the JVM starts (long blocking call ignored Ctrl-C, SIGTERM exited -6) - #494

Merged
tae898 merged 1 commit into
mainfrom
fix/sigint-open-transaction-hang
Oct 23, 2026
Merged

tae898 merged 1 commit into
mainfrom
fix/sigint-open-transaction-hang

Conversation

@tae898

@tae898 tae898 commented Oct 23, 2026

Copy link
Copy Markdown

A script with an open transaction (or none) that is blocked in one long call in the main thread did not stop on Ctrl-C on the JVM wheel; the native wheel stopped at once. This came out of the concurrency-contract probe 5 (midtx-sleep-catch: 5 of 15 JVM SIGINT runs "hung", SIGTERM exit -6). Data was never affected.

Cause

With the default interrupt=False, jpype.startJVM installs sun.misc.Signal handlers for SIGINT and SIGTERM (org.jpype.JPypeSignal), which replace Python's handlers at the operating-system level. A SIGINT now goes to the JVM, and the JVM's 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. The faulthandler dump of the hung probe runs shows the main thread in time.sleep(120) (p5_writer.py, line 28 in body), and a second signal (SIGUSR1 for the dump) made the pending KeyboardInterrupt appear straight away (rc 0, ki true). A busy pure-Python loop did not see the interrupt at all. The same handler explains the SIGTERM exit status -6: with no Python handler for SIGTERM it crashes in PyErr_SetInterruptEx (already documented, jpype-project/jpype#1497). The native wheel has no JPype and no Java signal handler, so the kernel delivers the signal to the main thread.

Fix

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, as documented); kill -TERM ends the process with -15 and no Python cleanup (committed data is intact, the next open replays the log, as after the -6 exits). interrupt=True is unchanged.

Before and after

Generated from /home/tk/build-scratch/sigint-work/*.out (wheel 26.10.2.post1 environment, main and this branch overlaid on its jars; child with a transaction holding one insert, SIGINT 0.3 s after the transaction opened, bound 10 s):

Case Before (main) After (this branch)
time.sleep(30) in the main thread, transaction open, SIGINT hung 20 of 20 (exit 0 only after the dump signal) 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

The published wheel itself: 10 of 10 hung for sleep with a transaction, 10 of 10 without one, 10 of 10 for a busy loop, SIGTERM -6 in 5 of 5. The Thread.sleep(1500) interrupt race (jpype-project/jpype#1496) did not reproduce on idle cores in either version (150 of 150 KeyboardInterrupt), so nothing is claimed about its rate; with JPype's handler no longer active it cannot occur.

Tests

  • tests/test_sigint.py: new 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 COUNT 0, finally, atexit) and test_sigterm_ends_the_process_with_status_minus_15. On this branch: 5 passed (the 3 existing plus 2 new). On main's code the new sleep test fails ("child still running 20 s after signal 2"), the 2 existing ones pass.
  • Not run locally: tests/test_python_function_interrupt.py (needs a wheel built from this tree for the bridge jar); CI covers it. It exercises a SIGINT inside a Python function called from a statement, which does not depend on JPype's handler.
  • Run through quiet_build.sh, with uv run.

Docs

docs/guide/known-issues.md (the Ctrl-C entry is rewritten: what was seen, what changed, a workaround for released versions), docs/api/jvm.md, docs/development/testing.md, and the start_jvm docstring.

To port

The same change must be ported to ArcadeData/arcadedb-python (tracking issue #19 there). Not ported here.

🤖 Generated with Claude Code

… (long blocking call with a transaction open ignored Ctrl-C; SIGTERM exited -6)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tae898
tae898 merged commit a9ed128 into main Oct 23, 2026
42 of 44 checks passed
@tae898
tae898 deleted the fix/sigint-open-transaction-hang branch October 23, 2026 23:03
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