Fix kill_process_tree reap wait crashing on pidfd EINVAL (#26964)
This commit is contained in:
@@ -1166,17 +1166,49 @@ def check_pkg_version_at_least(pkg: str, min_version: str) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _still_holding_resources(procs):
|
||||||
|
"""Procs still holding GPU context, pinned memory or fds.
|
||||||
|
|
||||||
|
A zombie has already had its resources freed by the kernel (only the exit
|
||||||
|
status lingers), so it counts as gone; NoSuchProcess / OSError (see
|
||||||
|
_wait_for_reap_or_raise) mean the same.
|
||||||
|
"""
|
||||||
|
alive = []
|
||||||
|
for p in procs:
|
||||||
|
try:
|
||||||
|
if p.is_running() and p.status() != psutil.STATUS_ZOMBIE:
|
||||||
|
alive.append(p)
|
||||||
|
except (psutil.NoSuchProcess, OSError):
|
||||||
|
pass
|
||||||
|
return alive
|
||||||
|
|
||||||
|
|
||||||
def _wait_for_reap_or_raise(procs, wait_timeout: float) -> None:
|
def _wait_for_reap_or_raise(procs, wait_timeout: float) -> None:
|
||||||
"""Wait for `procs` to exit; warn at ~10s, raise on `wait_timeout`.
|
"""Wait for `procs` to exit; warn at ~10s, raise on `wait_timeout`.
|
||||||
|
|
||||||
SIGKILL is asynchronous -- children hold GPU context, pinned memory and
|
SIGKILL is asynchronous -- children hold GPU context, pinned memory and
|
||||||
fds until the kernel reaps them. Raise on timeout so a stuck process
|
fds until the kernel reaps them. Raise on timeout so a stuck process
|
||||||
surfaces instead of leaving a latent race.
|
surfaces instead of leaving a latent race.
|
||||||
|
|
||||||
|
Polls /proc via is_running()/status() rather than psutil.wait_procs, whose
|
||||||
|
os.pidfd_open path (used for non-child procs) raises OSError(EINVAL) against
|
||||||
|
a just-killed process on some kernels and aborts the whole wait.
|
||||||
"""
|
"""
|
||||||
warn_at = min(10.0, wait_timeout / 2)
|
warn_at = min(10.0, wait_timeout / 2)
|
||||||
gone, alive = psutil.wait_procs(procs, timeout=warn_at)
|
deadline = time.monotonic() + wait_timeout
|
||||||
|
warn_deadline = time.monotonic() + warn_at
|
||||||
|
warned = False
|
||||||
|
while True:
|
||||||
|
alive = _still_holding_resources(procs)
|
||||||
if not alive:
|
if not alive:
|
||||||
return
|
return
|
||||||
|
now = time.monotonic()
|
||||||
|
if now >= deadline:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"kill_process_tree: {len(alive)} process(es) not reaped within "
|
||||||
|
f"{wait_timeout}s after SIGKILL; pids={[p.pid for p in alive]}"
|
||||||
|
)
|
||||||
|
if not warned and now >= warn_deadline:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"kill_process_tree: %d process(es) still alive after %.1fs SIGKILL; "
|
"kill_process_tree: %d process(es) still alive after %.1fs SIGKILL; "
|
||||||
"continuing to wait up to %.1fs total. pids=%s",
|
"continuing to wait up to %.1fs total. pids=%s",
|
||||||
@@ -1185,14 +1217,8 @@ def _wait_for_reap_or_raise(procs, wait_timeout: float) -> None:
|
|||||||
wait_timeout,
|
wait_timeout,
|
||||||
[p.pid for p in alive],
|
[p.pid for p in alive],
|
||||||
)
|
)
|
||||||
remaining = wait_timeout - warn_at
|
warned = True
|
||||||
if remaining > 0:
|
time.sleep(0.1)
|
||||||
_, alive = psutil.wait_procs(alive, timeout=remaining)
|
|
||||||
if alive:
|
|
||||||
raise RuntimeError(
|
|
||||||
f"kill_process_tree: {len(alive)} process(es) not reaped within "
|
|
||||||
f"{wait_timeout}s after SIGKILL; pids={[p.pid for p in alive]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def kill_process_tree(
|
def kill_process_tree(
|
||||||
|
|||||||
Reference in New Issue
Block a user