Repository navigation
fix(process): don't drop process output and the exit event from streams - #451
Open
TanishGudise wants to merge 1 commit into
Open
TanishGudise wants to merge 1 commit into
TanishGudise wants to merge 1 commit into
Conversation
A /process/{id}/stdout/stream client could miss the end of a process's
output and its exit event:
- The stream goroutine selected on outCh and doneCh. Once the process
exited both were ready, so it could return with output and the exit
event still queued.
- The waiter called cmd.Wait, which closes cmd.StdoutPipe/StderrPipe read
ends as soon as the process exits, discarding output the readers had not
read yet, and then queued the exit event ahead of the readers' last
chunks.
The API now owns the read ends of the output pipes, the waiter lets the
readers drain them before closing them and queuing the exit event (the
wait before closing is bounded by processOutputDrainTimeout, since a
background child can hold the pipes open), and the stream delivers
whatever is queued before it ends.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Checklist
Problem
GET /process/{process_id}/stdout/streamcan end without the last part of a process's output and without theexitevent (#450). There are two causes:outChanddoneChtogether, so once the process has exited it can return while events are still queued.cmd.Wait(), which closes theStdoutPipe/StderrPiperead ends as soon as the process exits, discarding output the readers haven't read, and then queuesexitahead of the readers' last chunks.Changes (
server/cmd/api/api/process.go)startWithOutputPipescreates the stdout/stderr pipes withos.Pipe, so the API owns the read ends andcmd.Waitdoesn't close them.exitedfromcmd.Waitright away. It then lets the readers drain the pipes before closing them and queuingexit, soexitcomes after all output. The wait before the pipes are closed is bounded byprocessOutputDrainTimeout(2 s), because a child the process started in the background can keep the pipes open. After that, it still waits for a reader blocked on a full queue, as main's blockingexitsend already did.doneChcloses, the stream goroutine sends whatever is still queued before it ends.Behavior changes:
sh -c "server &", or a shell killed while a child it started is still running), theexitevent now arrives up to 2 s after the exit instead of right away.statusstill showsexitedimmediately.Tests
New tests in
process_test.go, Linux (golang:1.26.4 container), 5 runs each:TestProcessStdoutStream_AfterExitDeliversQueuedEvents(200 short processes, stream opened after each exits)TestProcessStdoutStream_DeliversAllOutputBeforeExitTestProcessSpawn_BackgroundChildDoesNotHoldExit"Drain only" is main plus the stream change; "pipes only" is this PR without it. The third test guards the bounded wait: the shell exits while a background
sleep 30holds the pipes. It isn't aimed at these bugs, but it occasionally fails on main too, when the stream ends without the exit event.Repro sets from #450, against
kernel-images-apibuilt for linux/arm64 and called over HTTP on an internal Docker network, three sets each:echox3;exit 7), 100 runs: exit missing / output incompleteseq 1 3000; exit 3), 50 runs: exit missing / output incompletego vet ./...andgo test -race $(go list ./... | grep -v /e2e$)on Linux give the same results as main. The only failure on both isTestSetFilteredFailsWhenItCannotPersist, which can't pass as root in my container. I didn't runmake test-runtime(the Node daemon is unchanged) or the e2e suite: the published images are amd64-only and my Docker VM is arm64.🤖 Generated with Claude Code
Note
Medium Risk
Changes async process lifecycle, pipe I/O ordering, and SSE delivery timing; bounded drain can still drop unread output or delay exit events when background children hold pipes.
Overview
Fixes process stdout SSE streams ending without trailing output or the
exitevent (#450).Non-PTY spawns now use
startWithOutputPipes(os.Pipeinstead ofStdoutPipe/StderrPipe) socmd.Waitno longer closes pipe read ends while readers are still catching up. The exit waiter drains stdout/stderr readers (up toprocessOutputDrainTimeout, 2s) before closing FDs and enqueueingexit, so output events precede exit; scale-to-zero re-enable moves to after that drain.ProcessStdoutStreamflushes any events still onoutChafterdoneChcloses instead of stopping immediately.Tradeoff: background children that keep pipes open can delay the
exitSSE event by up to ~2s;statusstill reportsexitedright away.Adds regression tests for post-exit streaming, large buffered output, and background-child pipe behavior.
Reviewed by Cursor Bugbot for commit 58fc796. Bugbot is set up for automated code reviews on this repo. Configure here.