Merge pull request #6567 from stefanpieter/fix/sigusr1-stack-dump-no-restart
fix: keep SIGUSR1 stack dumps non-terminating
This commit is contained in:
commit
4b3fdde31b
2 changed files with 43 additions and 1 deletions
|
|
@ -268,8 +268,11 @@ def install_crash_visibility(
|
|||
sigusr1 = getattr(signal, "SIGUSR1", None)
|
||||
if sigusr1 is not None:
|
||||
try:
|
||||
# A diagnostic signal must not inherit SIGUSR1's default
|
||||
# terminate action after the dump. Chaining here made a
|
||||
# hang probe kill the server and trigger service restarts.
|
||||
faulthandler.register(
|
||||
sigusr1, file=_FAULT_STREAM, all_threads=True, chain=True
|
||||
sigusr1, file=_FAULT_STREAM, all_threads=True, chain=False
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -23,8 +23,12 @@ No live server socket is required — the helpers are exercised directly.
|
|||
"""
|
||||
import faulthandler
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -122,6 +126,41 @@ def test_install_is_idempotent(fresh_hooks):
|
|||
assert cv.install_crash_visibility(stream=stream, register_sigusr1_dump=False) is False
|
||||
|
||||
|
||||
@pytest.mark.skipif(not hasattr(signal, "SIGUSR1"), reason="SIGUSR1 is POSIX-only")
|
||||
def test_sigusr1_stack_dump_does_not_terminate_process():
|
||||
"""The diagnostic signal must dump stacks without chaining to SIG_DFL.
|
||||
|
||||
Chaining invokes SIGUSR1's default action after faulthandler writes the dump,
|
||||
terminating the WebUI process and prompting service managers to restart it.
|
||||
Exercise the real signal in a child so a regression cannot kill pytest.
|
||||
"""
|
||||
script = """
|
||||
import os
|
||||
import signal
|
||||
from api.crash_visibility import install_crash_visibility
|
||||
|
||||
# An ignored SIGUSR1 disposition survives exec on POSIX. Force the production
|
||||
# default so chain=True would terminate this child after writing the dump.
|
||||
signal.signal(signal.SIGUSR1, signal.SIG_DFL)
|
||||
install_crash_visibility()
|
||||
os.kill(os.getpid(), signal.SIGUSR1)
|
||||
print('survived-sigusr1', flush=True)
|
||||
"""
|
||||
completed = subprocess.run(
|
||||
[sys.executable, "-c", script],
|
||||
cwd=os.fspath(Path(__file__).resolve().parents[1]),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
check=False,
|
||||
)
|
||||
|
||||
assert completed.returncode == 0, completed.stderr
|
||||
assert "survived-sigusr1" in completed.stdout
|
||||
assert "Current thread" in completed.stderr
|
||||
assert "most recent call first" in completed.stderr
|
||||
|
||||
|
||||
# ── 2. threading.excepthook ─────────────────────────────────────────────────
|
||||
def test_thread_excepthook_installed(fresh_hooks):
|
||||
stream, _ = fresh_hooks
|
||||
|
|
|
|||
Loading…
Reference in a new issue