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)
|
sigusr1 = getattr(signal, "SIGUSR1", None)
|
||||||
if sigusr1 is not None:
|
if sigusr1 is not None:
|
||||||
try:
|
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(
|
faulthandler.register(
|
||||||
sigusr1, file=_FAULT_STREAM, all_threads=True, chain=True
|
sigusr1, file=_FAULT_STREAM, all_threads=True, chain=False
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,12 @@ No live server socket is required — the helpers are exercised directly.
|
||||||
"""
|
"""
|
||||||
import faulthandler
|
import faulthandler
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
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
|
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 ─────────────────────────────────────────────────
|
# ── 2. threading.excepthook ─────────────────────────────────────────────────
|
||||||
def test_thread_excepthook_installed(fresh_hooks):
|
def test_thread_excepthook_installed(fresh_hooks):
|
||||||
stream, _ = fresh_hooks
|
stream, _ = fresh_hooks
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue