From 0996a836557e13ba858321c57cb4b630dbbc5b3e Mon Sep 17 00:00:00 2001 From: goodboy Date: Thu, 30 Apr 2026 19:26:15 -0400 Subject: [PATCH] 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 --- scripts/tractor-reap | 61 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/scripts/tractor-reap b/scripts/tractor-reap index 3640d210..11ad8e09 100755 --- a/scripts/tractor-reap +++ b/scripts/tractor-reap @@ -23,6 +23,14 @@ Two cleanup phases (run in order when both are enabled): hard-crashing actor leaves leaked segments that nothing else GCs. +3. **UDS sweep** (`--uds` / `--uds-only`) — unlinks + `${XDG_RUNTIME_DIR}/tractor/@.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): --parent : descendant-mode — kill procs whose @@ -50,12 +58,18 @@ Usage: # only the shm sweep, skip process reap 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 scripts/tractor-reap --parent 12345 # dry-run: list what would be reaped, don't act scripts/tractor-reap -n - scripts/tractor-reap --shm -n + scripts/tractor-reap --shm --uds -n ''' import argparse @@ -118,7 +132,28 @@ def main() -> int: action='store_true', 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() + # 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 # package to be importable (e.g. when running from a @@ -129,14 +164,16 @@ def main() -> int: find_descendants, find_orphans, find_orphaned_shm, + find_orphaned_uds, reap, reap_shm, + reap_uds, ) rc: int = 0 - # --- phase 1: process reap (skipped under --shm-only) --- - if not args.shm_only: + # --- phase 1: process reap (skipped under --*-only) --- + if not skip_proc_reap: if args.parent is not None: pids: list[int] = find_descendants(args.parent) mode: str = f'descendants of PPid={args.parent}' @@ -173,6 +210,24 @@ def main() -> int: if errors: 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 # for CI health-check chaining. return rc