litellm/tests/test_litellm/test_process_helpers.py
devin-ai-integration[bot] 0d6ee3dc5a
test: count a zombie grandchild as gone in the migrate deploy timeout test (#42570)
* 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>
2026-09-22 14:59:26 -07:00

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()