Skip to content

new: Add a from-scratch ProcessRegistry alongside the existing one - #225

Open
milesj wants to merge 9 commits into
masterfrom
claude/starbase-process-registry-054e9e
Open

milesj wants to merge 9 commits into
masterfrom
claude/starbase-process-registry-054e9e

Conversation

@milesj

@milesj milesj commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds a second-generation ProcessRegistry under crates/process/src/registry/, exposed as the starbase_process::registry module so it sits next to the current registry without a name collision. It is split by concern: options, child (tracked child types), event, state (shared state and the shutdown algorithm), tasks (reaper and shutdown coordinator), tree (process-tree enumeration per platform), cache, and process_registry (the public type). The existing registry and the exec_* methods on Command are untouched; swapping them over (and then removing the old file) is a follow-up once the API is agreed on.

What the new registry does

  • Tracking: spawn a tokio Command (recording its command line) or track an existing Child. Children have a Running/Exited state and stay tracked until released, so a forced shutdown can still stop readers draining pipes held open by descendants.
  • Automatic cleanup: a reaper task detects exits via SIGCHLD on Unix, with an interval sweep as a safety net (and as the mechanism on Windows). Dropping a running child's original handle kills it as a cancellation (kill_on_drop); dropping an exited handle releases it.
  • Signals and events: OS termination signals feed a broadcast channel that a coordinator task turns into shutdowns. A ProcessEvent stream covers tracked/exited/released/signal/shutdown started/forced/finished.
  • Process trees: signal_descendants snapshots each child's descendants before signalling, so a shell wrapper's work is reached too. Linux reads /proc, macOS uses proc_listchildpids, other Unixes fall back to ps, Windows uses a ToolHelp snapshot + OpenProcess/TerminateProcess.
  • Shutdown: shutdown(signal) signals every running child, waits up to shutdown_threshold, force kills the rest, stops all output readers, and returns once everything has exited. A second signal escalates immediately. Routes through the coordinator when started, runs inline when stopped.
  • Start/stop/restart: shared state lives in an Arc borrowed by the tasks, so tasks can be moved between runtimes without losing tracked children, cache, or subscribers. Constructing outside a runtime leaves it stopped until start.
  • Singleton or not: instance/register(options)/try_instance, or new/with_options for independent registries.
  • Output cache: LRU cache plus exec_cached, which coalesces concurrent identical runs into one execution. Failures are not cached.

Supporting changes

  • SharedChild::try_wait (public): lets the reaper check for exit without tokio's wait, which closes stdin.
  • Race fix in SharedChild::wait: reaping a tokio Child via try_wait drops tokio's per-child SIGCHLD listener, so a task already inside wait lost its waker and hung. Found via stress testing; the existing kill_with_signal path has the same latent race. wait now also wakes on an exit recorded by try_wait.
  • Two extra windows-sys features for the ToolHelp snapshot.

Testing

  • 35 new tests in tests/registry_test.rs (Unix), covering lifecycle, tracking, reaping without closing stdin, drop semantics, single and tree signalling, graceful/forced/inline shutdown, events, and cache coalescing. Passed 20/20 stress runs after the race fix.
  • All existing crate suites pass; clippy (-D warnings), rustfmt, and rustdoc (-D warnings) are clean; the crate type-checks for x86_64-pc-windows-msvc. Linux was not compile-checked locally.
  • Reviewer note: exec_command_test::cancellation::force_kill_unblocks_continuous_input_with_inherited_stdin flakes roughly 1 in 10 runs on master as well (missing marker file); it is unrelated to this change.

🤖 Generated with Claude Code

milesj and others added 9 commits September 15, 2026 10:24
Adds `process_registry_new.rs`, exposed as the `process_registry_new`
module so it can be evaluated next to the current registry without
colliding with it. The `exec_*` paths on `Command` still use the old
registry; swapping them over is a follow-up.

The new registry owns the child lifetime end to end:

- Tracks children (`spawn`/`track`), reaps exits in the background via
  `SIGCHLD` with an interval sweep as a safety net, and keeps exited
  children tracked until released so a forced shutdown can still stop
  readers draining pipes held by descendants.
- Kills a running child when its original handle is dropped
  (cancellation), configurable via `kill_on_drop`.
- Forwards OS termination signals and programmatic `shutdown` to every
  running child and its descendants, with a grace period before force
  killing and immediate escalation on a second signal.
- Can `stop`/`start`/`restart` its background tasks without losing
  tracked children, cache, or subscribers.
- Works as a singleton (`instance`/`register`) or as independent
  instances.
- Caches output with `exec_cached`, coalescing concurrent identical runs.
- Emits lifecycle events over a broadcast channel.

Process trees are enumerated via `/proc` on Linux, `proc_listchildpids`
on macOS, `ps` on other Unixes, and a ToolHelp snapshot on Windows.

Also fixes a latent race in `SharedChild::wait`: reaping a tokio `Child`
through `try_wait` drops tokio's per-child `SIGCHLD` listener, so a
concurrent `wait` lost its waker and hung. `wait` now also wakes on an
exit recorded by `try_wait`. A public `SharedChild::try_wait` is added
for the reaper, as tokio's `wait` closes stdin.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Moves `process_registry_new.rs` into `src/registry/` with one file per
concern: options, tracked child types, events, the shared state and
shutdown algorithm, the background tasks, process tree enumeration, the
output cache, and the `ProcessRegistry` type itself. The module is now
`starbase_process::registry`; the public API is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…cess-registry-054e9e

* origin/master:
  build: Upgrade Rust to v1.99.
  chore: Release
  deps: Audit 09/21 (#226)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant