Skip to content

LineOutputHandler holds back a line ending in a bare CR until more output arrives, so a "100%\r" progress line is delivered seconds late (OutputDataReceived delivers it at once) #128

Closed

Description

@matt-edmondson

What's wrong

Since the #85 fix, when a chunk ends in \r, the handler keeps the whole line in its buffer. It waits for the next chunk to find out whether a \n follows. The code is in ProcessDataByLine (RunCommand/LineOutputHandler.cs:130-134, the if (i == data.Length - 1) break; branch) and CompletePendingCarriageReturn (:164-174).

If the command then goes quiet, the line is not delivered until its next write, or until EOF. Common cases:

  • progress output that ends in CR
  • a status line printed before a long step
  • a last CR-terminated message before a long wait

A caller that reacts to lines sees stale output for as long as the command is silent. Examples are a UI progress display, a "no output for N seconds" watchdog, or code that waits for a status string.

.NET's own AsyncStreamReader, which backs Process.OutputDataReceived, avoids both this delay and the #85 empty-line bug. It emits the line at the CR and remembers to skip one LF if the next chunk starts with one.

Reproduction (Linux, net10.0, HEAD b4116b5)

string script = "printf 'Downloading 100%%\\r'; sleep 3; printf 'Installed\\n'";
var sw = Stopwatch.StartNew();
await RunCommand.ExecuteAsync("sh", ["-c", script],
    new LineOutputHandler(l => Console.WriteLine($"[{sw.ElapsedMilliseconds}ms] {l}")));
// same script via Process.BeginOutputReadLine for comparison

Observed:

[ 3028ms] LineOutputHandler line: Downloading 100%
[ 3032ms] LineOutputHandler line: Installed
[    3ms] OutputDataReceived line: Downloading 100%
[ 3006ms] OutputDataReceived line: Installed

Expected: "Downloading 100%" is delivered at about 0 ms.

Suggested fix / acceptance criteria

  • At a CR, emit the line immediately, including when the CR is the last character of a chunk.
  • Keep a per-stream skipLeadingLf flag, so that a next chunk starting with \n drops that LF instead of emitting an empty line.
  • Reset() clears the flag. Complete() no longer needs to strip a trailing CR.
  • The CRLF-split tests from LineOutputHandler emits a spurious empty line when a CRLF line ending is split across two reads #85 still pass: "a\r" followed by "\nb\n" gives a, b, with no empty line.
  • New test: "a\r" on its own delivers a before any further chunk arrives and before Complete().

Activity

  1. matt-edmondson commented on Oct 31, 2026

    @matt-edmondson
    ContributorAuthor

    Triage


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions