mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-01 10:11:30 +08:00
fix(companion): Correct manager pid and reset companion signals
This commit is contained in:
parent
1827667cb2
commit
a90eba0c17
@ -25,6 +25,28 @@ if TYPE_CHECKING:
|
||||
PR_SET_PDEATHSIG = 1
|
||||
|
||||
|
||||
# Signals the arbiter and manager install handlers for; a forked companion
|
||||
# resets them to the default so its stop signal works and the target starts
|
||||
# from a clean slate, the same way a gunicorn worker does.
|
||||
INHERITED_SIGNALS = [
|
||||
"SIGCHLD", "SIGTERM", "SIGINT", "SIGHUP", "SIGQUIT",
|
||||
"SIGUSR1", "SIGUSR2", "SIGWINCH", "SIGTTIN", "SIGTTOU",
|
||||
]
|
||||
|
||||
|
||||
def reset_child_signals() -> None:
|
||||
"""Restore default signal handling in a freshly forked companion.
|
||||
|
||||
The companion inherits the manager's (and arbiter's) handlers across the
|
||||
fork. Without this, a stop signal like SIGTERM would hit the manager's
|
||||
handler -- which just flips a flag -- instead of terminating the companion.
|
||||
"""
|
||||
for name in INHERITED_SIGNALS:
|
||||
number = getattr(signal, name, None)
|
||||
if number is not None:
|
||||
signal.signal(number, signal.SIG_DFL)
|
||||
|
||||
|
||||
def set_parent_death_signal(stop_signal) -> bool:
|
||||
"""Ask the kernel to send ``stop_signal`` when this process's parent dies.
|
||||
|
||||
@ -79,6 +101,10 @@ class CompanionManager:
|
||||
signal on Linux and, as a portable fallback, watches ``getppid`` each
|
||||
tick so it never keeps companions running under a dead arbiter.
|
||||
"""
|
||||
# __init__ ran in the arbiter, so refresh pid/parent now that this is
|
||||
# the manager process: the companion parent-death guard compares its
|
||||
# getppid() against self.pid.
|
||||
self.pid = os.getpid()
|
||||
self.parent_pid = os.getppid()
|
||||
self._install_signals()
|
||||
set_parent_death_signal(signal.SIGTERM)
|
||||
@ -325,6 +351,7 @@ class CompanionManager:
|
||||
|
||||
try:
|
||||
self._close_manager_fds()
|
||||
reset_child_signals()
|
||||
set_parent_death_signal(signal.SIGTERM)
|
||||
if os.getppid() != self.pid:
|
||||
# Manager already died between fork and arming: do not run.
|
||||
|
||||
@ -9,7 +9,11 @@ from unittest import mock
|
||||
import pytest
|
||||
|
||||
from gunicorn.companion.control import CommandError
|
||||
from gunicorn.companion.manager import CompanionManager, set_parent_death_signal
|
||||
from gunicorn.companion.manager import (
|
||||
CompanionManager,
|
||||
reset_child_signals,
|
||||
set_parent_death_signal,
|
||||
)
|
||||
from gunicorn.companion.config import CompanionConfig
|
||||
from gunicorn.companion.process import State
|
||||
|
||||
@ -52,6 +56,16 @@ def test_log_exit_reports_signal_or_status():
|
||||
assert any("status" in message for message in messages)
|
||||
|
||||
|
||||
def test_reset_child_signals_restores_defaults():
|
||||
with mock.patch("signal.signal") as signal_signal:
|
||||
reset_child_signals()
|
||||
restored = {call.args[0]: call.args[1] for call in signal_signal.call_args_list}
|
||||
# The stop signal must reach the default disposition, not the manager's
|
||||
# inherited handler, so a forked companion actually terminates on SIGTERM.
|
||||
assert restored[signal.SIGTERM] is signal.SIG_DFL
|
||||
assert restored[signal.SIGINT] is signal.SIG_DFL
|
||||
|
||||
|
||||
def test_set_parent_death_signal_noop_off_linux():
|
||||
with mock.patch("sys.platform", "darwin"):
|
||||
assert set_parent_death_signal(signal.SIGTERM) is False
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user