Add `--uds`/`--uds-only` flags to `tractor-reap`

Wire up `find_orphaned_uds()` + `reap_uds()` from
`_reap` as a new phase-3 UDS sweep in the CLI
script. Opt-in via `--uds` (run after proc reap +
shm) or `--uds-only` (skip other phases).

Also,
- consolidate skip-proc-reap logic into a single
  `skip_proc_reap` bool covering both `--shm-only`
  and `--uds-only`
- extend header docstring + usage examples

(this commit msg was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
subint_forkserver_backend
Gud Boi 2026-04-30 19:26:15 -04:00
parent 1cdc7fb302
commit 0996a83655
1 changed files with 58 additions and 3 deletions

View File

@ -23,6 +23,14 @@ Two cleanup phases (run in order when both are enabled):
hard-crashing actor leaves leaked segments that hard-crashing actor leaves leaked segments that
nothing else GCs. nothing else GCs.
3. **UDS sweep** (`--uds` / `--uds-only`) — unlinks
`${XDG_RUNTIME_DIR}/tractor/<name>@<pid>.sock` files
whose binder pid is dead (or the `1616` registry
sentinel). Needed because the IPC server's
`os.unlink()` cleanup lives in a `finally:` block
that doesn't always run on hard exits (SIGKILL,
escaped `KeyboardInterrupt`, etc.) — see issue #452.
Process-reap detection modes (auto-selected): Process-reap detection modes (auto-selected):
--parent <pid> : descendant-mode — kill procs whose --parent <pid> : descendant-mode — kill procs whose
@ -50,12 +58,18 @@ Usage:
# only the shm sweep, skip process reap # only the shm sweep, skip process reap
scripts/tractor-reap --shm-only scripts/tractor-reap --shm-only
# process reap + shm + UDS sweep (the works)
scripts/tractor-reap --shm --uds
# only UDS sweep
scripts/tractor-reap --uds-only
# from inside a still-live supervisor # from inside a still-live supervisor
scripts/tractor-reap --parent 12345 scripts/tractor-reap --parent 12345
# dry-run: list what would be reaped, don't act # dry-run: list what would be reaped, don't act
scripts/tractor-reap -n scripts/tractor-reap -n
scripts/tractor-reap --shm -n scripts/tractor-reap --shm --uds -n
''' '''
import argparse import argparse
@ -118,7 +132,28 @@ def main() -> int:
action='store_true', action='store_true',
help='skip process reap; only do the shm sweep', help='skip process reap; only do the shm sweep',
) )
parser.add_argument(
'--uds',
action='store_true',
help=(
'after process reap, also unlink orphaned '
'${XDG_RUNTIME_DIR}/tractor/*.sock files '
'whose binder pid is dead (or the 1616 '
'registry sentinel). See issue #452.'
),
)
parser.add_argument(
'--uds-only',
action='store_true',
help='skip process reap + shm; only do the UDS sweep',
)
args = parser.parse_args() args = parser.parse_args()
# any *-only flag also skips the process reap phase
skip_proc_reap: bool = (
args.shm_only
or
args.uds_only
)
# import lazily so `--help` doesn't require the tractor # import lazily so `--help` doesn't require the tractor
# package to be importable (e.g. when running from a # package to be importable (e.g. when running from a
@ -129,14 +164,16 @@ def main() -> int:
find_descendants, find_descendants,
find_orphans, find_orphans,
find_orphaned_shm, find_orphaned_shm,
find_orphaned_uds,
reap, reap,
reap_shm, reap_shm,
reap_uds,
) )
rc: int = 0 rc: int = 0
# --- phase 1: process reap (skipped under --shm-only) --- # --- phase 1: process reap (skipped under --*-only) ---
if not args.shm_only: if not skip_proc_reap:
if args.parent is not None: if args.parent is not None:
pids: list[int] = find_descendants(args.parent) pids: list[int] = find_descendants(args.parent)
mode: str = f'descendants of PPid={args.parent}' mode: str = f'descendants of PPid={args.parent}'
@ -173,6 +210,24 @@ def main() -> int:
if errors: if errors:
rc = 1 rc = 1
# --- phase 3: UDS sweep (opt-in) ---
if args.uds or args.uds_only:
leaked_uds: list[str] = find_orphaned_uds()
if not leaked_uds:
print(
'[tractor-reap] no orphaned UDS sock-files '
'to sweep'
)
elif args.dry_run:
print(
f'[tractor-reap] dry-run — {len(leaked_uds)} '
f'orphaned UDS sock-file(s):\n {leaked_uds}'
)
else:
_, errors = reap_uds(leaked_uds)
if errors:
rc = 1
# exit 0 if everything cleaned cleanly, else 1 — useful # exit 0 if everything cleaned cleanly, else 1 — useful
# for CI health-check chaining. # for CI health-check chaining.
return rc return rc