mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-03 03:01:31 +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
|
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:
|
def set_parent_death_signal(stop_signal) -> bool:
|
||||||
"""Ask the kernel to send ``stop_signal`` when this process's parent dies.
|
"""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
|
signal on Linux and, as a portable fallback, watches ``getppid`` each
|
||||||
tick so it never keeps companions running under a dead arbiter.
|
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.parent_pid = os.getppid()
|
||||||
self._install_signals()
|
self._install_signals()
|
||||||
set_parent_death_signal(signal.SIGTERM)
|
set_parent_death_signal(signal.SIGTERM)
|
||||||
@ -325,6 +351,7 @@ class CompanionManager:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self._close_manager_fds()
|
self._close_manager_fds()
|
||||||
|
reset_child_signals()
|
||||||
set_parent_death_signal(signal.SIGTERM)
|
set_parent_death_signal(signal.SIGTERM)
|
||||||
if os.getppid() != self.pid:
|
if os.getppid() != self.pid:
|
||||||
# Manager already died between fork and arming: do not run.
|
# Manager already died between fork and arming: do not run.
|
||||||
|
|||||||
@ -9,7 +9,11 @@ from unittest import mock
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from gunicorn.companion.control import CommandError
|
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.config import CompanionConfig
|
||||||
from gunicorn.companion.process import State
|
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)
|
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():
|
def test_set_parent_death_signal_noop_off_linux():
|
||||||
with mock.patch("sys.platform", "darwin"):
|
with mock.patch("sys.platform", "darwin"):
|
||||||
assert set_parent_death_signal(signal.SIGTERM) is False
|
assert set_parent_death_signal(signal.SIGTERM) is False
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user