Skip to content

🐛 Settle join() only after output clears Stdio middleware - #248

Open
taras wants to merge 1 commit into
spike/use-processfrom
fix/join-settles-after-stdio-drain
Open

🐛 Settle join() only after output clears Stdio middleware#248
taras wants to merge 1 commit into
spike/use-processfrom
fix/join-settles-after-stdio-drain

Conversation

@taras

@taras taras commented Aug 12, 2026

Copy link
Copy Markdown
Member

Motivation

Fixes #244. On POSIX, join() and expect() read the useNativeProcess result directly, which settles on the child-process close event — while the Stdio middleware tasks may still be forwarding the final chunks. A caller could observe the exit status before all output had passed through its middleware and the public signals: finalize a decoder early, persist truncated output, or dismantle a middleware scope that still owns pending chunks. The win32 adapter already drained before resolving. Separately, a throwing Stdio handler crashed the host scope on posix and could deadlock the drain wait on win32.

Approach

Stacked on #247; supersedes #245, whose fix and regression tests are carried here. No changes to native.ts — the resource's close-settled result is correct; this is a middleware-layer change in the adapters:

  • posix gains the same drain-then-resolve task win32 already had: processResult settles only after the resource result plus both middleware tasks completing, so a blocked handler keeps join() pending. The two adapters are now symmetric. join() stays close-settled per process: no way to observe exit — join() settles on close (exit + stdio EOF), which can outlive the command #228's decision — the drain adds to the close wait, never replaces it — and Exec.join()/Exec.expect() inherit the guarantee.
  • On both platforms, a failing Stdio handler resolves processResult with Err, so join()/expect() throw the handler's error instead of hanging, and the middleware tasks always close their signals and resolve their done-resolvers in a synchronous finally, so teardown can never wait on a dead task.
  • Regression tests gate every chunk in middleware and release the gate only once the internal CloseEvent seam (src/exec/internal.ts, not exported from mod.ts) confirms the close event was received — join() therefore settles strictly after close yet must still show complete output, with no scheduler sleeps. Mutation-verified: deleting the posix drain fails both completeness tests deterministically. If a public exited() lands later (process: no way to observe exit — join() settles on close (exit + stdio EOF), which can outlive the command #228's original ask), the seam is deleted and the tests gate on that instead.

40/40 process tests × 3 runs on top of #247; typecheck/lint/format clean. The platform test matrix runs once this retargets to main after #247 merges.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0946c11f-4670-42fd-9d81-ddf7301845b7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Aug 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@effectionx/process@248

commit: f2964df

@taras
taras force-pushed the fix/join-settles-after-stdio-drain branch from 6110727 to 427ee01 Compare August 13, 2026 01:55
On POSIX, join()/expect() resolved directly from the useNativeProcess
result, which settles on the child-process close event while the Stdio
middleware tasks may still be forwarding the final chunks. Callers could
observe an exit status before all output had passed through their
middleware and the public signals.

- posix gains the same drain-then-resolve task win32 already had:
  processResult settles only after the close result plus both middleware
  tasks completing, so a blocked handler keeps join() pending
- both platforms: a failing Stdio handler resolves processResult with
  Err, so join()/expect() throw the handler's error instead of crashing
  the scope or hanging the drain, and the middleware tasks always close
  their signals and resolve their done-resolvers on the way out
- an internal CloseEvent context (not exported from mod.ts) lets the
  regression tests order assertions deterministically around the close
  event without scheduler sleeps

Fixes #244
@taras
taras force-pushed the fix/join-settles-after-stdio-drain branch from 427ee01 to f2964df Compare August 13, 2026 02:14
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.

process: join() settles before stdout/stderr pumps finish Stdio middleware

1 participant