[CollectLinux] Fix no-tty crash with console capability aware ProgressWriter#5771
Open
mdh1418 wants to merge 1 commit intodotnet:mainfrom
Open
[CollectLinux] Fix no-tty crash with console capability aware ProgressWriter#5771mdh1418 wants to merge 1 commit intodotnet:mainfrom
mdh1418 wants to merge 1 commit intodotnet:mainfrom
Conversation
…riter When CursorTop is 0 (e.g., in environments where cursor positioning isn't available), LineToClear was set to -1, causing SetCursorPosition to throw ArgumentOutOfRangeException on progress callbacks. Extract the progress display concern from OutputHandler into a nested ProgressWriter class that: - Probes console capability once at construction - Interactive: rewrites status line in-place with 1s throttle - Non-interactive: prints a static message once, silently no-ops after - Owns LineRewriter and LineToClear internally OutputHandler's status block reduces to progressWriter.Update(), fully decoupling it from LineRewriter. Matches CollectCommand's pattern of probing capability upfront and gating on it. Also adds bounds validation to MockConsole.SetCursorPosition and two regression tests for the CursorTop=0 case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a crash in dotnet-trace collect-linux progress callbacks in environments where console cursor positioning isn’t available (or behaves unexpectedly), by centralizing progress output behavior behind a capability-aware writer.
Changes:
- Refactors progress/status rendering in
CollectLinuxCommandinto a nestedProgressWriterthat gates rewrites and throttles updates. - Adds functional regression tests intended to cover
CursorTop == 0/ unsupported cursor repositioning scenarios. - Tightens
MockConsole.SetCursorPositionby adding row bounds validation.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs |
Introduces ProgressWriter to manage progress output and console rewrite capability checks. |
src/tests/dotnet-trace/CollectLinuxCommandFunctionalTests.cs |
Adds regression tests for cursor-top/cursor-repositioning edge cases. |
src/tests/Common/MockConsole.cs |
Adds bounds checking to better surface invalid cursor positioning in tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+604
to
+612
| // Only capture the cursor position to rewrite once we've committed to writing progress output, | ||
| // otherwise the position becomes stale the moment any other console output occurs. | ||
| LineRewriter rewriter = new(_console); | ||
| rewriter.LineToClear = _console.CursorTop - 1; | ||
|
|
||
| if (rewriter.IsRewriteConsoleLineSupported) | ||
| { | ||
| _rewriter = rewriter; | ||
| } |
|
|
||
| _nextUpdateTimestamp = now + Stopwatch.Frequency; | ||
| _console.Out.WriteLine($"[{_stopwatch.Elapsed:dd\\:hh\\:mm\\:ss}]\tRecording trace."); | ||
| _console.Out.WriteLine("Press <Enter> or <Ctrl-C> to exit..."); |
Comment on lines
+329
to
+371
| [ConditionalFact(nameof(IsCollectLinuxSupported))] | ||
| public void CollectLinuxCommand_DoesNotCrash_WhenCursorTopIsZero() | ||
| { | ||
| // Regression test: when CursorTop is 0 (e.g., no TTY), LineToClear = CursorTop - 1 = -1, | ||
| // which caused SetCursorPosition to throw ArgumentOutOfRangeException on the second | ||
| // progress callback when RewriteConsoleLine was called with the negative LineToClear. | ||
| MockConsole console = new(200, 30, _outputHelper); | ||
|
|
||
| var handler = new CollectLinuxCommandHandler(console); | ||
| handler.RecordTraceInvoker = (cmd, len, cb) => { | ||
| // Must send multiple callbacks — the crash occurred on the second one. | ||
| cb(3, IntPtr.Zero, UIntPtr.Zero); | ||
| cb(3, IntPtr.Zero, UIntPtr.Zero); | ||
| return 0; | ||
| }; | ||
|
|
||
| int exitCode = handler.CollectLinux(TestArgs()); | ||
| Assert.Equal((int)ReturnCode.Ok, exitCode); | ||
| } | ||
|
|
||
| [ConditionalFact(nameof(IsCollectLinuxSupported))] | ||
| public void CollectLinuxCommand_PrintsStatusOnce_WhenCursorRepositioningUnsupported() | ||
| { | ||
| // When cursor repositioning isn't supported, the status line should be | ||
| // printed exactly once — not spammed every second. | ||
| MockConsole console = new(200, 30, _outputHelper); | ||
|
|
||
| var handler = new CollectLinuxCommandHandler(console); | ||
| handler.RecordTraceInvoker = (cmd, len, cb) => { | ||
| for (int i = 0; i < 5; i++) | ||
| { | ||
| cb(3, IntPtr.Zero, UIntPtr.Zero); | ||
| } | ||
| return 0; | ||
| }; | ||
|
|
||
| int exitCode = handler.CollectLinux(TestArgs()); | ||
| Assert.Equal((int)ReturnCode.Ok, exitCode); | ||
|
|
||
| string[] lines = console.Lines; | ||
| int statusLineCount = lines.Count(l => l.Contains("Recording trace", StringComparison.OrdinalIgnoreCase)); | ||
| Assert.Equal(1, statusLineCount); | ||
| } |
Member
There was a problem hiding this comment.
(if we need control over timing we should use a TimeProvider rather than sleeps)
Comment on lines
72
to
82
| public void SetCursorPosition(int col, int row) | ||
| { | ||
| if (row < 0 || row >= WindowHeight) | ||
| { | ||
| throw new ArgumentOutOfRangeException(nameof(row), | ||
| row, | ||
| "The value must be greater than or equal to zero and less than the console's buffer size in that dimension."); | ||
| } | ||
| CursorTop = row; | ||
| _cursorLeft = col; | ||
| } |
noahfalk
approved these changes
Mar 19, 2026
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.
When CursorTop is 0 (e.g., in environments where cursor positioning isn't available), LineToClear was set to -1, causing SetCursorPosition to throw ArgumentOutOfRangeException on progress callbacks.
Extract the progress display concern from OutputHandler into a nested ProgressWriter class that:
OutputHandler's status block reduces to progressWriter.Update(), fully decoupling it from LineRewriter. Matches CollectCommand's pattern of probing capability upfront and gating on it.
Also adds bounds validation to MockConsole.SetCursorPosition and two regression tests for the CursorTop=0 case.