fix(proxy): stop blocking on orphaned stdio pipes after the child exits#2830
Open
isacgalvao wants to merge 1 commit into
Open
fix(proxy): stop blocking on orphaned stdio pipes after the child exits#2830isacgalvao wants to merge 1 commit into
isacgalvao wants to merge 1 commit into
Conversation
The proxy path joined its stdout/stderr reader threads unconditionally. Those threads only stop at pipe EOF, which never arrives when a descendant of the child (build daemon, background job) inherits the write end and outlives it - rtk kept running forever after the wrapped command had exited and its output was fully streamed. Once child.wait() returns, give the readers a 2s grace period to drain buffered output, then detach any reader still blocked on an orphaned pipe; process::exit() in main() reaps it. Captures are shared through Arc<Mutex<...>> so tracking still records what was streamed on detach. Fixes rtk-ai#2829
There was a problem hiding this comment.
Pull request overview
This PR fixes a long-standing hang in rtk proxy where the proxy process could block indefinitely after the wrapped command exited if any descendant process kept the inherited stdout/stderr pipe write-ends open (common with daemons/watchers/background jobs). The fix makes proxy completion driven by the direct child’s exit, with a bounded grace period for post-exit pipe draining, and adds a regression test for the orphaned-pipe scenario.
Changes:
- Update the proxy streaming implementation to join stdout/stderr reader threads with a 2s grace period after
child.wait(), then detach if still blocked on orphaned pipes. - Move captured stdout/stderr into shared buffers so tracking can still consume what was streamed before detaching.
- Add an integration test that reproduces the orphaned-pipe hang and asserts
rtk proxyreturns promptly with the child’s exit code (with a unix-only streamed-output assertion).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/main.rs |
Implements bounded post-exit joining/detaching of stdout/stderr reader threads and shared capture buffers to prevent hangs on orphaned pipes. |
tests/proxy_orphan_pipe_test.rs |
Adds an integration regression test that leaves an orphan process holding pipes open and verifies rtk proxy exits promptly and forwards the exit code. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
join()never returned. Deterministic repro and full analysis inrtk proxynever returns after the child exits when a descendant keeps the stdout/stderr pipes open (Windows repro, same mechanism as #2320) #2829 (same mechanism as rtk dotnet build/test/restore hangs forever when a long-lived child daemon (NuGet auth plugin / MSBuild node) inherits the captured stdout/stderr pipe #2320, reproduced on Windows and unix).child.wait()returns, the reader threads are now joined with a 2s grace period and detached if still blocked on an orphaned pipe;std::process::exit()inmain()reaps them. Captured output moved to shared buffers so tracking still records exactly what was streamed when a reader is detached.rtk proxyreturns promptly with the child's exit code (and, on unix, that the child's output was streamed through).Fixes #2829
Test plan
cargo fmt --all && cargo clippy --all-targets && cargo test(Windows 11, stable)proxy_exits_when_child_dies_despite_orphaned_pipefails ondevelop(hangs until the 15s deadline) and passes with this changertk proxy cmd /c "start /b waitfor ZZZ /t 20 & exit /b 0"— before: returns after ~20.2s (when the orphan dies; forever with a real daemon); after: returns in ~2s with exit code 0 and identical outputrtk proxy git status,rtk proxy cmd /c "echo hi & exit /b 3"— output and exit codes unchanged, no added latency on the normal EOF path