Repository navigation
Implement signal.set_wakeup_fd and run pending signal handlers after select/poll - #7
Conversation
9bcb06b to
07726a7
Compare
…select/poll
signal.set_wakeup_fd was a stub returning -1. asyncio relies on it: a
subprocess transport without pipes is only completed from the SIGCHLD
handler that loop.add_signal_handler installs, and the loop's poll() is
only woken by the byte CPython's C signal handler writes to the wakeup fd.
In GraalPy the Python handler is queued as an async action that runs when
the main thread next reaches a safepoint, which never happens while it is
blocked in a native poll() with no timeout, unless the kernel happened to
deliver the signal to that very thread (EINTR). So
test_asyncio.test_events.PollEventLoopTests.test_subprocess_exitcode,
test_subprocess_close_after_finish and test_subprocess_wait_no_same_group
hung in 8 of 48 local runs under load, and regularly in the tagged unittest
gates on both linux-amd64 and linux-aarch64.
- set_wakeup_fd stores the fd (validated like CPython: must be called from
the main thread, must be a valid fd in non-blocking mode) and returns the
previous one. The Java-level signal handler writes the signal number to
it before queueing the Python handler, as CPython's trip_signal() does.
- PosixSupport.writeSignalWakeupByte() does that write without creating
Python exceptions or TruffleStrings from native memory, because the JVM's
signal dispatcher thread has not entered the context; the native backend
returns errno directly. A failed write is reported from the main thread
through sys.unraisablehook ("Exception ignored when trying to write to
the signal wakeup fd"), honouring warn_on_full_buffer.
- raise_signal waits (bounded) for the handler of a signal raised in this
process to run before returning, since the JVM dispatches signals on
another thread; CPython runs it synchronously.
- select.select and poll.poll run a pending signal handler before
returning, so a handler's exception interrupts the call as in CPython
(the wakeup byte otherwise makes the call return normally first).
Adds tests/test_signal.py::WakeupFdTests, which block in a native poll()
with no timeout and rely on the wakeup byte, and tags the now passing
CPython WakeupFDTests/WakeupSignalTests on linux. With this change the
three asyncio tests above no longer hang (0 of 48 runs).
07726a7 to
0f47bec
Compare
|
Rebased onto Conflicts. Upstream's POSIX refactor in #6 (
The behavior is the same as before. The signal dispatcher thread writes the wakeup byte, then queues the Python handler, and pending handlers run when select/poll returns. This PR doesn't touch The second commit ("Fix pre-existing style gate failures on elide-25.5.x") was dropped. Local testing (sibling graal =
Follow-up, not in this PR: #6 untagged these three asyncio tests for the GitHub tag sets until this PR landed. Once it merges, they can be re-tagged. |
|
CI note: the first attempt of run 38793369213 had one failure, |
Problem
signal.set_wakeup_fdwas a stub returning -1 (upstream too). asyncio relies on it: a subprocess transport without pipes is only completed from the SIGCHLD handler installed withloop.add_signal_handler, and the loop'spoll()is only woken by the byte CPython's C signal handler writes to the wakeup fd. In GraalPy the Python handler is queued as an async action that runs when the main thread next reaches a safepoint, which never happens while it is blocked in a nativepoll()with no timeout, unless the kernel happened to deliver the signal to that very thread (EINTR).Result:
test_asyncio.test_events.PollEventLoopTests.test_subprocess_exitcode,test_subprocess_close_after_finishandtest_subprocess_wait_no_same_grouphang until the 600s timeout in the tagged unittest gates on linux-amd64 and linux-aarch64 (they are untagged for the*-githubtag sets in #6 for now). Locally (JVM standalone, 6 parallel loops) 8 of 48 runs hung; a hung process has a zombiesh, the main thread inNativePosixSupport.polland no other Python thread.Change
set_wakeup_fdstores the fd (validated like CPython: main thread, valid fd, non-blocking) and returns the previous one. The Java-level signal handler writes the signal number to it before queueing the Python handler, as CPython'strip_signal()does.PosixSupport.writeSignalWakeupByte()does that write without creating Python exceptions or TruffleStrings from native memory, because the JVM's signal dispatcher thread has not entered the context; the native backend returns errno directly. A failed write is reported from the main thread throughsys.unraisablehook("Exception ignored when trying to write to the signal wakeup fd"), honouringwarn_on_full_buffer.raise_signalwaits (bounded) for the handler of a signal raised in this process to run before returning, since the JVM dispatches signals on another thread; CPython runs it synchronously.select.selectandpoll.pollrun a pending signal handler before returning, so a handler's exception interrupts the call as in CPython (the wakeup byte otherwise makes the call return normally first).Verification
tests/test_signal.py::WakeupFdTests(block in a nativepoll()with no timeout and rely on the wakeup byte): pass.test_signal.WakeupFDTests+WakeupSignalTests: all pass (3 consecutive runs;test_pendingskipped, needspthread_sigmask); now tagged on linux.mx checkstyleclean.Not for the upstream-sync PR (#6); separate runtime change.