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,
|
grace: float = 3.0,
|
||||||
poll: float = 0.25,
|
poll: float = 0.25,
|
||||||
log=print,
|
log=print,
|
||||||
|
include_descendants: bool = True,
|
||||||
) -> tuple[list[int], list[int]]:
|
) -> tuple[list[int], list[int]]:
|
||||||
'''
|
'''
|
||||||
Deliver SIGINT to each pid, wait up to `grace`
|
Deliver SIGINT to each pid (AND its subtree
|
||||||
seconds for them to exit, then SIGKILL any that
|
descendants when `include_descendants=True`, the
|
||||||
survive.
|
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
|
Returns `(signalled, survivors_killed)` so callers
|
||||||
can report / assert.
|
can report / assert.
|
||||||
|
|
@ -480,8 +489,43 @@ def reap(
|
||||||
if not pids:
|
if not pids:
|
||||||
return ([], [])
|
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] = []
|
signalled: list[int] = []
|
||||||
for pid in pids:
|
for pid in pids_to_signal:
|
||||||
try:
|
try:
|
||||||
os.kill(pid, signal.SIGINT)
|
os.kill(pid, signal.SIGINT)
|
||||||
signalled.append(pid)
|
signalled.append(pid)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue