aboutsummaryrefslogtreecommitdiff
path: root/app/tests/test_backends_servers.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/tests/test_backends_servers.py')
-rw-r--r--app/tests/test_backends_servers.py37
1 files changed, 31 insertions, 6 deletions
diff --git a/app/tests/test_backends_servers.py b/app/tests/test_backends_servers.py
index 9fd71f2..283df95 100644
--- a/app/tests/test_backends_servers.py
+++ b/app/tests/test_backends_servers.py
@@ -370,19 +370,44 @@ class ReapTests(unittest.TestCase):
class KillPidTests(unittest.TestCase):
- """_kill_pid: the reap check ends the grace wait before SIGKILL."""
+ """_kill_pid: the group probe decides the wait's end, not the reap.
- def test_reaped_child_ends_wait_without_sigkill(self):
+ Reaping the launcher proves nothing about the rest of its process
+ group: workers can outlive it and must still get the SIGKILL
+ escalation.
+ """
+
+ def test_empty_group_ends_wait_without_sigkill(self):
+ # Single-process server: the launcher is reaped and the group is
+ # empty (killpg(0) raises) — success immediately, no SIGKILL.
with patch("os.getpgid", return_value=4242), \
- patch("os.killpg") as mk_killpg, \
- patch("os.waitpid", return_value=(4242, 0)) as mk_waitpid, \
+ patch("os.killpg",
+ side_effect=[None, ProcessLookupError]) as mk_killpg, \
+ patch("os.waitpid", return_value=(4242, 0)), \
patch("time.sleep") as mk_sleep:
ok = servers._kill_pid(4242)
self.assertTrue(ok)
- mk_killpg.assert_called_once_with(4242, signal.SIGTERM)
- mk_waitpid.assert_called_once_with(4242, os.WNOHANG)
+ self.assertEqual(mk_killpg.call_args_list[0].args,
+ (4242, signal.SIGTERM))
+ self.assertEqual(mk_killpg.call_args_list[-1].args, (4242, 0))
mk_sleep.assert_not_called()
+ def test_reaped_leader_with_live_workers_escalates_to_sigkill(self):
+ # A launcher that dies on SIGTERM while its workers ignore it:
+ # reaping the leader must not end the stop, the surviving group
+ # keeps burning the grace period and then gets SIGKILLed.
+ with patch("os.getpgid", return_value=4242), \
+ patch("os.killpg", return_value=None) as mk_killpg, \
+ patch("os.waitpid", return_value=(4242, 0)), \
+ patch("time.sleep"):
+ ok = servers._kill_pid(4242)
+ self.assertTrue(ok)
+ calls = mk_killpg.call_args_list
+ self.assertEqual(calls[0].args, (4242, signal.SIGTERM))
+ self.assertEqual(calls[-1].args, (4242, signal.SIGKILL))
+ # The group was probed repeatedly while waiting for the workers.
+ self.assertGreater(len(calls), 2)
+
def test_escalates_to_sigkill_when_child_stays_alive(self):
with patch("os.getpgid", return_value=4242), \
patch("os.killpg") as mk_killpg, \