mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-25 01:02:15 +00:00
* test: count a zombie grandchild as gone in the migrate deploy timeout test A SIGKILLed grandchild whose parent died in the same killpg reparents to PID 1 or the nearest subreaper and stays a zombie until reaped, and signal 0 still succeeds on a zombie, so the timeout test read it as alive wherever PID 1 is slow to reap or never does. The sibling test in tests/test_litellm/proxy/db already handled that; both now share one process_is_gone helper that reads the /proc state and reaps its own children, with unit tests for the live, reaped, unreaped, and foreign zombie shapes. * test: move the pre-commit interrupt test onto the shared zombie-aware liveness helper --------- Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
65 lines
2.1 KiB
Python
65 lines
2.1 KiB
Python
"""``process_is_gone`` has to say gone for every shape a killed process can take, and never for a live one."""
|
|
|
|
import os
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Final
|
|
|
|
import pytest
|
|
|
|
from tests._process_helpers import process_is_gone
|
|
|
|
SLEEP_FOREVER: Final = (sys.executable, "-I", "-c", "import time; time.sleep(600)")
|
|
|
|
LEAVE_A_ZOMBIE_BEHIND: Final = """
|
|
import os, signal, subprocess, sys, time
|
|
grandchild = subprocess.Popen([sys.executable, "-c", "import time; time.sleep(600)"])
|
|
os.kill(grandchild.pid, signal.SIGKILL)
|
|
while open(f"/proc/{grandchild.pid}/stat").read().rpartition(")")[2].split()[0] != "Z":
|
|
time.sleep(0.01)
|
|
print(grandchild.pid, flush=True)
|
|
time.sleep(600)
|
|
"""
|
|
|
|
|
|
def test_a_live_process_is_not_gone() -> None:
|
|
child: Final = subprocess.Popen(SLEEP_FOREVER)
|
|
try:
|
|
assert not process_is_gone(child.pid, within_seconds=0.3)
|
|
finally:
|
|
child.kill()
|
|
child.wait()
|
|
|
|
|
|
def test_a_reaped_child_is_gone() -> None:
|
|
child: Final = subprocess.Popen(SLEEP_FOREVER)
|
|
child.kill()
|
|
child.wait()
|
|
assert process_is_gone(child.pid, within_seconds=1)
|
|
|
|
|
|
@pytest.mark.skipif(os.name == "nt", reason="zombies are a POSIX thing")
|
|
def test_an_unreaped_child_is_reaped_and_gone() -> None:
|
|
child: Final = subprocess.Popen(SLEEP_FOREVER)
|
|
os.kill(child.pid, signal.SIGKILL)
|
|
assert process_is_gone(child.pid, within_seconds=1)
|
|
with pytest.raises(ChildProcessError):
|
|
os.waitpid(child.pid, os.WNOHANG)
|
|
|
|
|
|
@pytest.mark.skipif(not Path("/proc").is_dir(), reason="needs procfs to see a zombie that is not our child")
|
|
def test_a_zombie_left_by_another_process_is_gone() -> None:
|
|
zombie_factory: Final = subprocess.Popen(
|
|
[sys.executable, "-I", "-c", LEAVE_A_ZOMBIE_BEHIND], stdout=subprocess.PIPE, text=True
|
|
)
|
|
try:
|
|
assert zombie_factory.stdout is not None
|
|
zombie_pid: Final = int(zombie_factory.stdout.readline())
|
|
with pytest.raises(ChildProcessError):
|
|
os.waitpid(zombie_pid, os.WNOHANG)
|
|
assert process_is_gone(zombie_pid, within_seconds=1)
|
|
finally:
|
|
zombie_factory.kill()
|
|
zombie_factory.wait()
|