Skip to content

fix(windows): confirm job and descendant completion before returning - #55

Closed
glendigity wants to merge 6 commits into
watchexec:mainfrom
glendigity:codex/windows-job-lifetime
Closed

fix(windows): confirm job and descendant completion before returning#55
glendigity wants to merge 6 commits into
watchexec:mainfrom
glendigity:codex/windows-job-lifetime

Conversation

@glendigity

Copy link
Copy Markdown

Windows JobObject waits can report completion while a descendant is still running after the leader exits. The existing completion-port helper accepts any notification, try_wait reports only the leader status, and both frontends cache that status before confirming job completion.

This repair tracks process-creation notifications, retains process handles until they signal exit, and reconciles the observed process count against job accounting. Both wait and try_wait cache completion only after the whole observed job has finished. Lost or inconsistent notification evidence returns an I/O error instead of reporting successful cleanup.

The retained handles matter: an earlier accounting-only implementation fixed normal waits but still returned early after TerminateJobObject. Both std and Tokio cancellation regression tests detected that distinction on native Windows.

Tokio uses finite background waits with independently owned job/port handles and shared observation state. Cancelling a wait future cannot leave an indefinite worker borrowing handles that the child wrapper subsequently closes. into_inner preserves its existing detached-job behavior while releasing the added observation resources.

No public API or dependency version changes are required. The existing job-object feature additionally enables the Windows SystemServices bindings for notification constants.

The synthetic fixture launches this test executable as a leader and descendant, with all standard streams disconnected. A handshake holds the descendant alive after the leader exits; independently retained process handles verify liveness and cleanup. Coverage includes nonblocking waits, normal completion, explicit termination, repeated waits, a cancelled Tokio wait, inconsistent observation counts and invalid handles.

Verification at 699d18b6c6e6a926f27c807a87b9fb1fa3bc8dc4:

  • Native Windows 10.0.26100.0, x86_64-pc-windows-msvc, Rust 1.97.1: formatting and strict Clippy passed; all-feature/all-target tests passed (99 passed, two existing subprocess helpers ignored).
  • Applied only the new integration tests to unchanged upstream 1d1cc53c5c3f9c2e8bf4ae0459d780a616dc1015; the leader-exit try_wait regression failed with the expected live-descendant assertion.
  • A separate native public-API probe changed from three lifetime violations to zero: try_wait and wait remain incomplete while the descendant is alive, and termination plus wait completes with the descendant signalled exited.
  • macOS all-feature/all-target tests and formatting passed. Strict macOS Clippy is blocked by the existing unused nix::errno::Errno import in src/std/core.rs:378; that unrelated import is unchanged.

The finite wait interval is not a hard operating-system termination deadline. Spawn/setup-failure cleanup is outside this change.

References:

@glendigity glendigity closed this Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant