Add subtree-walk to `reap()` for full actor-tree teardown
`reap(include_descendants=True)` now expands each orphan-root pid into its full psutil subtree before delivering SIGINT, so a multi-level leaked actor-tree gets torn down in a single pass instead of requiring repeated calls (each pass kills the current `ppid==1` level, the level below becomes init-adopted, etc.). Falls back to the original flat `pids` list when `psutil` is unavailable. Emits a log line when expansion adds descendant pids. (this commit msg was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-codesubint_forkserver_backend
parent
fb87c36263
commit
8de684f5de
|
|
@ -463,11 +463,20 @@ def reap(
|
|||
grace: float = 3.0,
|
||||
poll: float = 0.25,
|
||||
log=print,
|
||||
include_descendants: bool = True,
|
||||
) -> tuple[list[int], list[int]]:
|
||||
'''
|
||||
Deliver SIGINT to each pid, wait up to `grace`
|
||||
seconds for them to exit, then SIGKILL any that
|
||||
survive.
|
||||
Deliver SIGINT to each pid (AND its subtree
|
||||
descendants when `include_descendants=True`, the
|
||||
default), wait up to `grace` seconds for them to
|
||||
exit, then SIGKILL any that survive.
|
||||
|
||||
The subtree-walk is what makes a single `acli.reap`
|
||||
invocation tear down a *full* leaked actor-tree
|
||||
rather than just its init-adopted top. Without it,
|
||||
repeated calls are needed: each pass kills the
|
||||
current `ppid==1` level, the level below becomes
|
||||
init-adopted, next pass kills those, etc.
|
||||
|
||||
Returns `(signalled, survivors_killed)` so callers
|
||||
can report / assert.
|
||||
|
|
@ -480,8 +489,43 @@ def reap(
|
|||
if not pids:
|
||||
return ([], [])
|
||||
|
||||
# Expand each pid into its full subtree (descendants
|
||||
# included) so a multi-level leaked actor-tree gets
|
||||
# torn down in a single pass. Falls back to the
|
||||
# original `pids` list if psutil isn't installed.
|
||||
pids_to_signal: list[int] = list(pids)
|
||||
if include_descendants:
|
||||
try:
|
||||
import psutil
|
||||
except ImportError:
|
||||
psutil = None
|
||||
if psutil is not None:
|
||||
seen: set[int] = set(pids)
|
||||
for root in list(pids):
|
||||
try:
|
||||
p = psutil.Process(root)
|
||||
for c in p.children(recursive=True):
|
||||
if c.pid not in seen:
|
||||
seen.add(c.pid)
|
||||
pids_to_signal.append(c.pid)
|
||||
except (
|
||||
psutil.NoSuchProcess,
|
||||
psutil.AccessDenied,
|
||||
):
|
||||
# raced / unprivileged — skip silently;
|
||||
# the orphan-root itself still gets the
|
||||
# signal below.
|
||||
continue
|
||||
n_extra: int = len(pids_to_signal) - len(pids)
|
||||
if n_extra:
|
||||
log(
|
||||
f'[tractor-reap] expanded {len(pids)} '
|
||||
f'orphan-root(s) → {len(pids_to_signal)} '
|
||||
f'incl. {n_extra} subtree-descendant(s)'
|
||||
)
|
||||
|
||||
signalled: list[int] = []
|
||||
for pid in pids:
|
||||
for pid in pids_to_signal:
|
||||
try:
|
||||
os.kill(pid, signal.SIGINT)
|
||||
signalled.append(pid)
|
||||
|
|
|
|||
Loading…
Reference in New Issue