Fix Console.ReadLine() returning each line one Enter late with TreatControlCAsInput on Windows - #133896
Open
caraioniurie47 wants to merge 1 commit into
Open
Fix Console.ReadLine() returning each line one Enter late with TreatControlCAsInput on Windows#133896caraioniurie47 wants to merge 1 commit into
Console.ReadLine() returning each line one Enter late with TreatControlCAsInput on Windows#133896caraioniurie47 wants to merge 1 commit into
Conversation
With Console.TreatControlCAsInput set, ENABLE_PROCESSED_INPUT is clear and the Windows console host ends a line with a lone '\r'. Console.In wrapped a plain StreamReader, whose ReadLine reads ahead after '\r' to look for a '\n'. On a console that read waits for the next line, so each ReadLine returned only after the following Enter. Add ConsoleStreamReader, used on Windows when input is not redirected, the same condition under which Unix uses StdInReader. Its ReadLine ends the line at '\r' without reading ahead, and the next read skips a '\n' that pairs with it. Zero-length reads and reads with invalid arguments leave that skip pending, so they return or throw without waiting. Redirected input keeps the plain StreamReader. Add unit tests for the reader and a Windows manual test. Fix dotnet#133814 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
|
Tagging subscribers to this area: @dotnet/area-system-console |
This was referenced Sep 14, 2026
Author
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.
Fixes #133814
With
Console.TreatControlCAsInput = true, which clearsENABLE_PROCESSED_INPUT, the Windows console host ends each line with a lone\rinstead of\r\n.Console.Inwraps aStreamReader, andStreamReader.ReadLine()reads ahead after a\rto check for a\n. On a console that read waits for the next line, so eachReadLine()returned only after the following Enter.Change. A new internal
ConsoleStreamReader, compiled for Windows only, ends a line at\rwithout reading ahead, and skips a\nthat pairs with that\rat the start of the nextReadLine,Peek,ReadorReadToEnd. A zero-length read leaves the skip pending, so it still returns without waiting.ConsolePal.Windows.GetOrCreateReaderuses the new reader only whenConsole.IsInputRedirectedis false, the same condition under whichConsolePal.UnixusesStdInReader; redirected input keeps the plainStreamReader.Behaviour change. On a Windows console, a line ending in a lone
\ris now returned as soon as the\rarrives, not after the next line. In the default mode the console sends\r\n, andReadLine()returns the same lines at the same Enter as before. Whether this needs thebreaking-changelabel is left to the area owners.Why not in
StreamReaderor the console stream. Neither was built. The same change inStreamReader.ReadLine()would apply to every stream, not only a console. Translating\rto\r\nin the console stream would also change the bytesConsole.OpenStandardInput()returns. The new reader reads a character at a time, becauseStreamReader's buffer is not accessible from System.Console.Performance
Reading a character at a time makes
ReadLineslower thanStreamReader.ReadLine, which searches its buffer for the line end. BenchmarkDotNet 0.15.2, .NET 10.0.12, Windows 11 x64. The type is internal and the benchmark process's input is redirected, so this branch'sConsoleStreamReader.csis compiled into the benchmark and compared withStreamReader. Each operation reads just over 40,000 chars of lines ending\r\nfrom aMemoryStreamto the end (UTF-8,bufferSize: 4096asConsole.ReadBufferSize):StreamReaderConsoleStreamReaderReadLine()ReadLine()Read()Read()Read(char[], 0, 4096)Read(char[], 0, 4096)That is about 4 ns per character in
ReadLine, with the same allocation. The reader is used only for console input that is not redirected.Tests
ConsoleStreamReaderTests, new, 23 cases, compiling the reader's source in asKeyParserTestsdoes. They cover lone\r,\nand\r\nendings; a\rand its\narriving in separate reads; a line longer than the buffers; a surrogate pair and a two-byte UTF-8 character split across reads;Peek,Read,ReadBlock,ReadToEndand span reads after a\r; and zero-length reads returning, and invalid arguments throwing, without a read from the stream. With theReadLineoverride disabled, 3 of the 23 fail,ReadLine_LineEndingInCarriageReturn_DoesNotReadPastIt,Read_OfNoCharacters_DoesNotWaitForInputandRead_WithInvalidArguments_ThrowsWithoutWaitingForInput, each with the test stream throwing because it was read past the\r; the others pass either way, since their streams never make a read wait.ReadLineWithTreatControlCAsInput, a new manual test, reads two lines with the property set. With the reader disabled inGetOrCreateReaderit failed at the secondAssert.Equal(Expected: "two",Actual: "") in a driven run; with it, it passes driven and by hand.System.Console.Tests: 4341 total, 0 failed.Console.ReadLine()returns each line one Enter late whenConsole.TreatControlCAsInputistrueon Windows #133814 repro, run on a local build under inbox conhost: eachReadLine()returns its line right after its own Enter; on .NET 10.0.12 it still lags one line. The local build does the same underOpenConsole.exe1.0.200517002, run by hand.\r-only and\r\nline endings: output identical to .NET 10.0.12.Not run: Linux and macOS, where
Console.InisStdInReader, which this change does not touch; the manual test is Windows-only. Release configuration was not run either.Note
AI-generated, written at my direction and reviewed by me before posting. Everything ran on Windows 11 x64 (build 26200). The tests and console runs used a local build of this branch, Debug libraries on a Checked runtime; the benchmark ran as described above. Console runs used inbox conhost unless named. The manual test was run both with keystrokes written into the console from another process (
AttachConsole+WriteConsoleInput) and typed by hand; the repro was driven that way under conhost and typed by hand under OpenConsole.