Skip to content

Batch 5: kill-tree race + partial-failure, SingleInstance ownership, exceptional shutdown - #110

Merged
rezdm merged 1 commit into
mainfrom
fix/batch5-kill-lifecycle
Jul 22, 2026
Merged

Batch 5: kill-tree race + partial-failure, SingleInstance ownership, exceptional shutdown#110
rezdm merged 1 commit into
mainfrom
fix/batch5-kill-lifecycle

Conversation

@rezdm

@rezdm rezdm commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Batch 5 — the safety-critical cluster (process-kill path + lifecycle). Written deliberately; the kill-path changes are compile-validated here and confirmed across the CI matrix, but not runtime-tested.

Closes #79
Closes #81
Closes #82

#81 — kill_process_tree (mirrored across linux/freebsd/macos/solaris)

  • PID-reuse race: the root's token was validated once up front, then the tree was rebuilt from fresh kernel/proc state. If the root exited and its PID was reused in that window, the recycled process became the accepted identity and its whole tree could be signaled. Now the root is re-validated against the caller's start-time token after enumeration, and a mismatch aborts.
  • Partial-failure reporting: a signal failing (e.g. EPERM) on some tree member now returns success=false with a clear message, instead of success=true with the shortfall buried in error_message that both UIs can dismiss. A "skipped" (PID-reused-since-scan) count stays informational.

#79 — SingleInstance

  • Only unlink() the socket if this instance actually bound it (owns_socket_). A secondary launch computed the same path but never bound it — its destructor deleted the running primary's socket, so a third launch became a wrongful second primary.
  • Bound the listener's per-connection read (SO_RCVTIMEO 2s) so a silent client can't wedge the listener thread and hang the destructor's join().

#82 — GUI lifecycle

  • ImGuiApp: worker shutdown + callback detachment moved into an idempotent shutdown_workers(), called from both run()'s tail and ~ImGuiApp(). An exception escaping run() after the workers start no longer leaves them calling back into a destroyed app.
  • main(): the SingleInstance raise callback (captures &app) is now detached via a scope guard declared after app, so it's cleared before app dies on the exception path too.

Testing

Built GUI+TUI+tests on Linux; unit tests pass (62 checks). All four killer backends + the lifecycle changes compiled by the CI matrix. Kill-path behavior warrants runtime validation before relying on it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Xodto1bRjCSGEzLuz3JFbV

…exceptional shutdown (#79, #81, #82)

Safety-critical batch — kill path and lifecycle.

#81 kill_process_tree, mirrored across all four Unix backends (linux, freebsd,
macos, solaris):
- Re-validate the ROOT against the caller's start-time token AFTER enumeration.
  Previously the token was checked once up front, then the tree was rebuilt
  from fresh kernel/proc state; if the root exited and its PID was reused in
  that window, the recycled process became the accepted identity and its whole
  tree could be signaled. Now a post-enumeration identity mismatch aborts.
- Surface partial failure: a signal that failed (e.g. EPERM) on some tree
  member now returns success=false with a clear message, instead of
  success=true with the shortfall buried in error_message that both UIs can
  dismiss. A "skipped" (PID-reused-since-scan) count is informational, not a
  failure.

#79 SingleInstance:
- Only unlink the socket file if THIS instance actually bound+listened
  (owns_socket_). A secondary launch computes the same path but never bound it;
  its destructor used to delete the running primary's socket, so a third launch
  couldn't find the primary and wrongly became a second primary.
- Bound the listener's per-connection read (SO_RCVTIMEO 2s) so a client that
  connects but never writes can't wedge the listener thread and hang the
  destructor's join().

#82 GUI lifecycle:
- ImGuiApp: worker shutdown + callback detachment moved into an idempotent
  shutdown_workers(), called from both run()'s tail and ~ImGuiApp(). If run()
  throws after the workers start, the destructor now stops them and detaches
  the data_store/name_resolver callbacks that capture `this`.
- main(): the SingleInstance raise callback (captures &app) is detached via a
  scope guard declared after `app`, so it's cleared before `app` is destroyed
  on the exception path too, not only on normal return.

Built GUI+TUI+tests on Linux; 62 checks pass. FreeBSD/Solaris/macOS killer
edits validated by CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xodto1bRjCSGEzLuz3JFbV
@rezdm
rezdm merged commit 03cc398 into main Jul 22, 2026
12 checks passed
@rezdm
rezdm deleted the fix/batch5-kill-lifecycle branch July 22, 2026 08:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant