Repository navigation
Split each run's output into lines in a handler of its own [patch] - #132
Merged
Merged
Conversation
A LineOutputHandler shared by two overlapping ExecuteAsync runs kept one pair of line buffers for both: the second run's Reset discarded the first run's partial line, and both runs then appended to the same StringBuilder from different threads, joining their text into one line. RunAsync now asks the handler for a per-run handler (CreateRunHandler); LineOutputHandler returns a fresh one with the same delegates and encoding, so each run's line state is its own. Fixes #130 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017vfJW4ekcBueRdQ8TsyWTk
|
matt-edmondson
pushed a commit
that referenced
this pull request
Nov 4, 2026
Resolves the conflict in LineOutputHandler.cs: keeps this branch's immediate CR delivery with its per-stream skip-leading-LF state, and takes main's CreateRunHandler (#132), its class remarks, and its narrowing of IsLineBreak to LF only (#136). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017vfJW24pNCmCLDhz6z3ZdY
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.



A
LineOutputHandlerkept its line-splitting state,outputBufferanderrorBuffer, on the instance. When two runs shared one handler and overlapped:Reset()threw away the first run's partial line.StringBuilderfrom different threads. Their text came out joined ("BBBBAAAA" | "BBBB"), and the unsynchronised writes could corrupt the builder.This takes the per-run fix the issue prefers:
OutputHandlergainsinternal virtual OutputHandler CreateRunHandler() => this.LineOutputHandleroverrides it to return a newLineOutputHandlerwith the same delegates and encoding, and empty buffers.RunAsyncfeeds each run tooutputHandler.CreateRunHandler(), so every run has its own line state and nothing one run leaves behind reaches another.Reset()is still called on the run handler, so a stateful handler that returns itself keeps today's behaviour. The public API is unchanged.The delegates are still serialised per run, by the existing
deliveryLock, not across runs. I chose not to take a handler-wide lock: a delegate that starts a nested run on the same handler would deadlock on it.OutputHandler's remarks now say the never-at-the-same-time guarantee covers one run, and that state shared between concurrent runs needs its own lock, as the repro'slock (lines)already does.Tests
LineOutputHandlerSharedByOverlappingRunsShouldKeepEachRunsLinesApart: run A printsAAAA, waits, then finishes its line. Run B starts on the same handler while A is mid-line. The test expects exactlyAAAAAAAAandBBBBBBBB.ReusedLineOutputHandlerShouldDropTheLineARunWhoseCallbackThrewLeftUnfinishedhad a precondition that the failed run's partial line sat in the shared handler's buffer. That partial line now lives in the run's own handler, so the precondition now asserts that the shared buffer is empty. The test's actual check, that the next run starts clean, is unchanged.Related: #129 (bare CR delivery) touches
LineOutputHandlerbuffering too, but inProcessDataByLine, not where the buffers are owned. Whichever lands second should rebase cleanly. #125 is a separate cancellation-timing issue and is not addressed here.Fixes #130
Generated by Claude Code