What's wrong
When a read faults, AsyncProcessStreamReader.Start breaks out of its read loop (RunCommand/AsyncProcessStreamReader.cs:74-77) and stops reading both pipes. A read faults when the handler callback throws, or when the handler's Encoding uses throwOnInvalidBytes: true, a case the code explicitly supports ("carries a decode error out to the caller").
RunCommand.cs:484-485 then awaits:
await Task.WhenAll(outputReader.Start(ct), process.WaitForExitAsync(ct));
WhenAll doesn't complete until the process exits, and nothing kills the process. Once the child writes more than the pipe buffer holds (≈64 KB on Linux, less on Windows), it blocks on write() forever. The call never returns, and the exception is never surfaced.
Reproduction (Linux, net10.0)
new OutputHandler(s => throw new InvalidOperationException()) with sh -c "echo first; sleep 0.5; head -c 1000000 /dev/zero" → the task is still pending after 10 s, and the head process is still alive.
encoding: new UTF8Encoding(false, throwOnInvalidBytes: true) with sh -c "printf '\377\n'; sleep 0.5; head -c 1000000 /dev/zero" → the task is still pending after 10 s.
Suggested fix
In RunAsync, observe the reader task on its own. If it faults, call the existing TryKill(process) (killing the whole process tree), then await the exit and rethrow the read exception. For example:
Task read = outputReader.Start(ct);
try { await read; } catch { TryKill(process); throw; }
await process.WaitForExitAsync(ct);
Acceptance criteria
Both repros throw the handler or decode exception promptly (well under a second after the fault), and no child process is left running.
What's wrong
When a read faults,
AsyncProcessStreamReader.Startbreaks out of its read loop (RunCommand/AsyncProcessStreamReader.cs:74-77) and stops reading both pipes. A read faults when the handler callback throws, or when the handler'sEncodingusesthrowOnInvalidBytes: true, a case the code explicitly supports ("carries a decode error out to the caller").RunCommand.cs:484-485then awaits:WhenAlldoesn't complete until the process exits, and nothing kills the process. Once the child writes more than the pipe buffer holds (≈64 KB on Linux, less on Windows), it blocks onwrite()forever. The call never returns, and the exception is never surfaced.Reproduction (Linux, net10.0)
new OutputHandler(s => throw new InvalidOperationException())withsh -c "echo first; sleep 0.5; head -c 1000000 /dev/zero"→ the task is still pending after 10 s, and theheadprocess is still alive.encoding: new UTF8Encoding(false, throwOnInvalidBytes: true)withsh -c "printf '\377\n'; sleep 0.5; head -c 1000000 /dev/zero"→ the task is still pending after 10 s.Suggested fix
In
RunAsync, observe the reader task on its own. If it faults, call the existingTryKill(process)(killing the whole process tree), then await the exit and rethrow the read exception. For example:Acceptance criteria
Both repros throw the handler or decode exception promptly (well under a second after the fault), and no child process is left running.