[core] Fix driver SIGABRT + core dump when SIGTERM arrives during a slow graceful shutdown - #64830
[core] Fix driver SIGABRT + core dump when SIGTERM arrives during a slow graceful shutdown#64830Ti1mmy wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request wraps the SIGTERM handler in Ray to cancel any active SIGALRM timer (using signal.alarm(0)), preventing abseil's failure signal handler from aborting the process during a slow graceful shutdown. A regression test has also been added. The reviewer correctly identified a potential TypeError if the provided sigterm_handler is not callable (such as signal.SIG_DFL or signal.SIG_IGN), and suggested adding a check for callability and verifying the existence of signal.alarm for better compatibility.
|
|
||
| def sigterm_handler_wrapper(signum, frame): | ||
| # abseil's failure signal handler (Ray registers SIGTERM with it) | ||
| # has already armed a SIGALRM timer that SIGABRTs the process | ||
| # after 3s. Cancel it, since handlers installed here recover into | ||
| # a graceful shutdown that may legitimately take longer. | ||
| signal.alarm(0) | ||
| sigterm_handler(signum, frame) | ||
|
|
||
| signal.signal(signal.SIGTERM, sigterm_handler_wrapper) |
There was a problem hiding this comment.
If sigterm_handler is not a callable (for example, if it is signal.SIG_DFL or signal.SIG_IGN), wrapping it in sigterm_handler_wrapper and calling sigterm_handler(signum, frame) will raise a TypeError when the signal is received. To prevent this, we should check if sigterm_handler is callable before wrapping it. Additionally, checking hasattr(signal, "alarm") ensures compatibility with non-standard environments where signal.alarm might not be available.
if callable(sigterm_handler):
def sigterm_handler_wrapper(signum, frame):
# abseil's failure signal handler (Ray registers SIGTERM with it)
# has already armed a SIGALRM timer that SIGABRTs the process
# after 3s. Cancel it, since handlers installed here recover into
# a graceful shutdown that may legitimately take longer.
if hasattr(signal, "alarm"):
signal.alarm(0)
sigterm_handler(signum, frame)
signal.signal(signal.SIGTERM, sigterm_handler_wrapper)
else:
signal.signal(signal.SIGTERM, sigterm_handler)There was a problem hiding this comment.
Pull request overview
This PR addresses a SIGTERM shutdown edge case where Ray’s SIGTERM handler recovers into a graceful Python exit while Abseil’s failure-signal handler has already armed a 3s SIGALRM abort timer, leading to SIGABRT + large core dumps during slow graceful shutdowns (e.g., Jupyter kernel restarts).
Changes:
- Cancel any pending POSIX
alarm()in Ray’s SIGTERM handler path (viaset_sigterm_handler) to prevent Abseil’s abort timer from firing during graceful shutdown. - Add a regression test that reproduces the slow-graceful-exit-over-3s scenario and asserts the driver exits cleanly with SIGTERM (15), not SIGABRT.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| python/ray/_private/utils.py | Wraps the POSIX SIGTERM handler to call signal.alarm(0) before executing the existing graceful handler. |
| python/ray/tests/test_ray_shutdown.py | Adds a regression test to ensure SIGTERM during a slow graceful exit does not result in SIGABRT/core dump. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for _ in range(30): | ||
| if p.stdout.readline().strip() == b"ready": | ||
| break | ||
| else: | ||
| raise AssertionError("Driver never became ready.") |
503347e to
1df90f8
Compare
…andlers Ray registers SIGTERM as an abseil failure signal (RayLog::InstallFailureSignalHandler), so abseil arms a 3s SIGALRM abort timer before chaining into the Python-level SIGTERM handlers installed via set_sigterm_handler(), all of which recover into a graceful shutdown. When that shutdown outlives the timer (e.g. a driver-owned local node being torn down after a process-group SIGTERM, as on every Jupyter kernel restart), the process dies with SIGABRT and dumps a multi-GB core file instead of exiting cleanly. Cancel the pending alarm before invoking the graceful handler. The abort-timer protection for genuine crash signals (SIGSEGV etc.) is unaffected. Fixes ray-project#64829 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Timothy Zheng <timothy.zheng@uwaterloo.ca>
Poll stdout with select() under a wall-clock deadline so a hung ray.init() fails the test with a clear assertion instead of blocking readline() until the pytest timeout. Addresses review feedback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Timothy Zheng <timothy.zheng@uwaterloo.ca>
Signed-off-by: Timothy Zheng <timothy.zheng@uwaterloo.ca>
1df90f8 to
da08955
Compare
Signed-off-by: Timothy Zheng <timothy.zheng@uwaterloo.ca>
feff112 to
ccd34e4
Compare
Description
On POSIX, Ray registers SIGTERM with abseil's failure signal handler:
ray/src/ray/util/logging.cc
Line 437 in 2a70ac4
Here,
logging.ccsets onlycall_previous_handlerandwriterfnon the options struct, leaves every other option at its default —alarm_on_failure_secsdefaults to 3 seconds — and immediately passes it toabsl::InstallFailureSignalHandler:ray/src/ray/util/logging.cc
Lines 506 to 509 in 2a70ac4
So when SIGTERM arrives, abseil's handler runs first and arms a 3-second abort timer before chaining to the previous handler (
absl/debugging/failure_signal_handler.cc#L405-L409):The chained handler is Ray's Python SIGTERM handler, which recovers into a graceful
sys.exit()that tears down the driver-owned local node. A Jupyter notebook kernel restart typically takes more than 3 seconds to clean up (the kernel's Ray daemons received the same process-group SIGTERM, so the driver stalls waiting on them while they die). So before the cleanup finishes, the alarm goes off and sends the ABORT signal — and that SIGABRT is what ends up creating the multi-GB core dump in the notebook's working directory on every restart.Fix: cancel the pending alarm (
signal.alarm(0)) before invoking any handler installed viaray._private.utils.set_sigterm_handler(). Every handler installed through that function recovers into a graceful shutdown, so this applies to exactly the affected code paths; abseil's abort-timer protection for genuine crash signals (SIGSEGV, etc.) is unaffected.Includes a regression test: a driver whose exit outlives the 3s timer must exit gracefully with code 15 instead of being killed by SIGABRT.
Related issues
Fixes #64829
Additional information
Verification (the fix is pure Python; validated by applying this exact patch to the released 2.56.0 wheel):
-6(SIGABRT) 3.0s after SIGTERM before the fix → clean exit15after (3/3 runs)pytest python/ray/tests/test_ray_shutdown.py -k test_sigterm_during_slow_graceful_exit_does_not_abort: fails (-6) before, passes afterpre-commit run --files python/ray/_private/utils.py python/ray/tests/test_ray_shutdown.py: all hooks passDuplicate-work check: no linked PRs on #64829 and no open PRs found for this area.
AI assistance disclosure: prepared with AI assistance (Claude Code); the submitter reviewed every changed line and ran the repro and tests locally.
🤖 Generated with Claude Code