From 47ce0d26b62e521cd3b09258c51acb8bdfc324f0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Oct 2025 19:29:24 +0000
Subject: [PATCH 01/11] Initial plan
From 682a87d29413d7c029c74f3113cb0ee39525d88a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Oct 2025 19:37:05 +0000
Subject: [PATCH 02/11] Add --diagnostic-port option to dotnet-stack report
command
Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com>
---
src/Tools/dotnet-stack/ReportCommand.cs | 66 +++++++++++++++++++------
1 file changed, 52 insertions(+), 14 deletions(-)
diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs
index c8f2ecc365..c1287843c2 100644
--- a/src/Tools/dotnet-stack/ReportCommand.cs
+++ b/src/Tools/dotnet-stack/ReportCommand.cs
@@ -27,22 +27,35 @@ internal static class ReportCommandHandler
/// The process to report the stack from.
/// The name of process to report the stack from.
/// The duration of to trace the target for.
+ /// The diagnostic port to connect to.
///
- private static async Task Report(CancellationToken ct, TextWriter stdOutput, TextWriter stdError, int processId, string name, TimeSpan duration)
+ private static async Task Report(CancellationToken ct, TextWriter stdOutput, TextWriter stdError, int processId, string name, TimeSpan duration, string diagnosticPort)
{
string tempNetTraceFilename = Path.Join(Path.GetTempPath(), Path.GetRandomFileName() + ".nettrace");
string tempEtlxFilename = "";
try
{
- // Either processName or processId has to be specified.
+ // Validate that only one of processId, name, or diagnosticPort is specified
+ int optionCount = 0;
+ if (processId != 0) optionCount++;
+ if (!string.IsNullOrEmpty(name)) optionCount++;
+ if (!string.IsNullOrEmpty(diagnosticPort)) optionCount++;
+
+ if (optionCount == 0)
+ {
+ stdError.WriteLine("--process-id, --name, or --diagnostic-port is required");
+ return -1;
+ }
+ else if (optionCount > 1)
+ {
+ stdError.WriteLine("Only one of --process-id, --name, or --diagnostic-port can be specified");
+ return -1;
+ }
+
+ // Resolve process name to ID if needed
if (!string.IsNullOrEmpty(name))
{
- if (processId != 0)
- {
- Console.WriteLine("Can only specify either --name or --process-id option.");
- return -1;
- }
processId = CommandUtils.FindProcessIdWithName(name);
if (processId < 0)
{
@@ -55,14 +68,31 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput
stdError.WriteLine("Process ID should not be negative.");
return -1;
}
- else if (processId == 0)
+
+ DiagnosticsClient client;
+ if (!string.IsNullOrEmpty(diagnosticPort))
{
- stdError.WriteLine("--process-id is required");
- return -1;
+ try
+ {
+ IpcEndpointConfig diagnosticPortConfig = IpcEndpointConfig.Parse(diagnosticPort);
+ if (!diagnosticPortConfig.IsConnectConfig)
+ {
+ stdError.WriteLine("--diagnostic-port only supports connect mode to a runtime.");
+ return -1;
+ }
+ client = new DiagnosticsClient(diagnosticPortConfig);
+ }
+ catch (Exception ex)
+ {
+ stdError.WriteLine($"--diagnostic-port argument error: {ex.Message}");
+ return -1;
+ }
+ }
+ else
+ {
+ client = new DiagnosticsClient(processId);
}
-
- DiagnosticsClient client = new(processId);
List providers = new()
{
new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational)
@@ -189,7 +219,8 @@ public static Command ReportCommand()
{
ProcessIdOption,
NameOption,
- DurationOption
+ DurationOption,
+ DiagnosticPortOption
};
reportCommand.SetAction((parseResult, ct) => Report(ct,
@@ -197,7 +228,8 @@ public static Command ReportCommand()
stdError: parseResult.Configuration.Error,
processId: parseResult.GetValue(ProcessIdOption),
name: parseResult.GetValue(NameOption),
- duration: parseResult.GetValue(DurationOption)));
+ duration: parseResult.GetValue(DurationOption),
+ diagnosticPort: parseResult.GetValue(DiagnosticPortOption)));
return reportCommand;
}
@@ -221,5 +253,11 @@ public static Command ReportCommand()
{
Description = "The name of the process to report the stack."
};
+
+ private static readonly Option DiagnosticPortOption =
+ new("--diagnostic-port", "--dport")
+ {
+ Description = "The path to a diagnostic port to be used."
+ };
}
}
From 1c97f1194daf030609dad62a8e168bf5ecb992d5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Oct 2025 19:38:05 +0000
Subject: [PATCH 03/11] Add test for --diagnostic-port option registration
Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com>
---
src/tests/dotnet-stack/StackTests.cs | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/src/tests/dotnet-stack/StackTests.cs b/src/tests/dotnet-stack/StackTests.cs
index c689e5a5ba..cff55e2fc8 100644
--- a/src/tests/dotnet-stack/StackTests.cs
+++ b/src/tests/dotnet-stack/StackTests.cs
@@ -100,5 +100,26 @@ public async Task ReportsStacksCorrectly(TestConfiguration config)
Assert.True(correctStackParts[j] == stackParts[i], $"{correctStackParts[j]} != {stackParts[i]}");
}
}
+
+ [Fact]
+ public void DiagnosticPortOptionIsRegistered()
+ {
+ Command reportCommand = ReportCommandHandler.ReportCommand();
+
+ // Verify the diagnostic port option is registered
+ bool hasDiagnosticPortOption = false;
+ foreach (var option in reportCommand.Options)
+ {
+ if (option.Name == "diagnostic-port")
+ {
+ hasDiagnosticPortOption = true;
+ // Verify it has the short alias --dport
+ Assert.Contains("--dport", option.Aliases);
+ break;
+ }
+ }
+
+ Assert.True(hasDiagnosticPortOption, "The --diagnostic-port option should be registered in the report command");
+ }
}
}
From 07c8f6cb36fef336c1fb94830f3fc8af4ecade66 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Oct 2025 22:04:46 +0000
Subject: [PATCH 04/11] Add braces around if statements on lines 41-43
Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com>
---
src/Tools/dotnet-stack/ReportCommand.cs | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs
index c1287843c2..0a027d175b 100644
--- a/src/Tools/dotnet-stack/ReportCommand.cs
+++ b/src/Tools/dotnet-stack/ReportCommand.cs
@@ -38,9 +38,18 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput
{
// Validate that only one of processId, name, or diagnosticPort is specified
int optionCount = 0;
- if (processId != 0) optionCount++;
- if (!string.IsNullOrEmpty(name)) optionCount++;
- if (!string.IsNullOrEmpty(diagnosticPort)) optionCount++;
+ if (processId != 0)
+ {
+ optionCount++;
+ }
+ if (!string.IsNullOrEmpty(name))
+ {
+ optionCount++;
+ }
+ if (!string.IsNullOrEmpty(diagnosticPort))
+ {
+ optionCount++;
+ }
if (optionCount == 0)
{
From a72e9210199ee4b432709db45abc78157e500c99 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Oct 2025 22:15:37 +0000
Subject: [PATCH 05/11] Add dotnet-stack to InternalsVisibleTo in
NETCore.Client
Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com>
---
.../Microsoft.Diagnostics.NETCore.Client.csproj | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj b/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj
index c006330b90..098fb2813b 100644
--- a/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj
+++ b/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj
@@ -41,6 +41,7 @@
+
From 877a49d9171be8547c69f7738d11ff15ebad04c9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 9 Oct 2025 01:46:42 +0000
Subject: [PATCH 06/11] Use DiagnosticsClientBuilder to support both connect
and listen modes
Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com>
---
src/Tools/dotnet-stack/ReportCommand.cs | 149 +++++++++++-------------
1 file changed, 68 insertions(+), 81 deletions(-)
diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs
index 0a027d175b..c7fd982112 100644
--- a/src/Tools/dotnet-stack/ReportCommand.cs
+++ b/src/Tools/dotnet-stack/ReportCommand.cs
@@ -78,107 +78,94 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput
return -1;
}
- DiagnosticsClient client;
- if (!string.IsNullOrEmpty(diagnosticPort))
+ DiagnosticsClientBuilder builder = new("dotnet-stack", 10);
+ using (DiagnosticsClientHolder holder = await builder.Build(ct, processId, diagnosticPort, showChildIO: false, printLaunchCommand: false).ConfigureAwait(false))
{
- try
+ if (holder == null)
{
- IpcEndpointConfig diagnosticPortConfig = IpcEndpointConfig.Parse(diagnosticPort);
- if (!diagnosticPortConfig.IsConnectConfig)
- {
- stdError.WriteLine("--diagnostic-port only supports connect mode to a runtime.");
- return -1;
- }
- client = new DiagnosticsClient(diagnosticPortConfig);
- }
- catch (Exception ex)
- {
- stdError.WriteLine($"--diagnostic-port argument error: {ex.Message}");
return -1;
}
- }
- else
- {
- client = new DiagnosticsClient(processId);
- }
- List providers = new()
- {
- new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational)
- };
+ DiagnosticsClient client = holder.Client;
- // collect a *short* trace with stack samples
- // the hidden '--duration' flag can increase the time of this trace in case 10ms
- // is too short in a given environment, e.g., resource constrained systems
- // N.B. - This trace INCLUDES rundown. For sufficiently large applications, it may take non-trivial time to collect
- // the symbol data in rundown.
- EventPipeSession session = await client.StartEventPipeSessionAsync(providers, requestRundown:true, token:ct).ConfigureAwait(false);
- using (session)
- using (FileStream fs = File.OpenWrite(tempNetTraceFilename))
- {
- Task copyTask = session.EventStream.CopyToAsync(fs, ct);
- await Task.Delay(duration, ct).ConfigureAwait(false);
- await session.StopAsync(ct).ConfigureAwait(false);
+ List providers = new()
+ {
+ new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational)
+ };
- // check if rundown is taking more than 5 seconds and add comment to report
- Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(5));
- Task completedTask = await Task.WhenAny(copyTask, timeoutTask).ConfigureAwait(false);
- if (completedTask == timeoutTask)
+ // collect a *short* trace with stack samples
+ // the hidden '--duration' flag can increase the time of this trace in case 10ms
+ // is too short in a given environment, e.g., resource constrained systems
+ // N.B. - This trace INCLUDES rundown. For sufficiently large applications, it may take non-trivial time to collect
+ // the symbol data in rundown.
+ EventPipeSession session = await client.StartEventPipeSessionAsync(providers, requestRundown:true, token:ct).ConfigureAwait(false);
+ using (session)
+ using (FileStream fs = File.OpenWrite(tempNetTraceFilename))
{
- stdOutput.WriteLine($"# Sufficiently large applications can cause this reportCommand to take non-trivial amounts of time");
+ Task copyTask = session.EventStream.CopyToAsync(fs, ct);
+ await Task.Delay(duration, ct).ConfigureAwait(false);
+ await session.StopAsync(ct).ConfigureAwait(false);
+
+ // check if rundown is taking more than 5 seconds and add comment to report
+ Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(5));
+ Task completedTask = await Task.WhenAny(copyTask, timeoutTask).ConfigureAwait(false);
+ if (completedTask == timeoutTask)
+ {
+ stdOutput.WriteLine($"# Sufficiently large applications can cause this reportCommand to take non-trivial amounts of time");
+ }
+ await copyTask.ConfigureAwait(false);
}
- await copyTask.ConfigureAwait(false);
- }
- // using the generated trace file, symbolicate and compute stacks.
- tempEtlxFilename = TraceLog.CreateFromEventPipeDataFile(tempNetTraceFilename);
- using (SymbolReader symbolReader = new(TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath })
- using (TraceLog eventLog = new(tempEtlxFilename))
- {
- MutableTraceEventStackSource stackSource = new(eventLog)
+ // using the generated trace file, symbolicate and compute stacks.
+ tempEtlxFilename = TraceLog.CreateFromEventPipeDataFile(tempNetTraceFilename);
+ using (SymbolReader symbolReader = new(TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath })
+ using (TraceLog eventLog = new(tempEtlxFilename))
{
- OnlyManagedCodeStacks = true
- };
+ MutableTraceEventStackSource stackSource = new(eventLog)
+ {
+ OnlyManagedCodeStacks = true
+ };
- SampleProfilerThreadTimeComputer computer = new(eventLog, symbolReader);
- computer.GenerateThreadTimeStacks(stackSource);
+ SampleProfilerThreadTimeComputer computer = new(eventLog, symbolReader);
+ computer.GenerateThreadTimeStacks(stackSource);
- Dictionary> samplesForThread = new();
+ Dictionary> samplesForThread = new();
- stackSource.ForEach((sample) => {
- StackSourceCallStackIndex stackIndex = sample.StackIndex;
- while (!stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false).StartsWith("Thread ("))
- {
- stackIndex = stackSource.GetCallerIndex(stackIndex);
- }
+ stackSource.ForEach((sample) => {
+ StackSourceCallStackIndex stackIndex = sample.StackIndex;
+ while (!stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false).StartsWith("Thread ("))
+ {
+ stackIndex = stackSource.GetCallerIndex(stackIndex);
+ }
- // long form for: int.Parse(threadFrame["Thread (".Length..^1)])
- // Thread id is in the frame name as "Thread ()"
- string template = "Thread (";
- string threadFrame = stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false);
+ // long form for: int.Parse(threadFrame["Thread (".Length..^1)])
+ // Thread id is in the frame name as "Thread ()"
+ string template = "Thread (";
+ string threadFrame = stackSource.GetFrameName(stackSource.GetFrameIndex(stackIndex), false);
- // we are looking for the first index of ) because
- // we need to handle a thread name like: Thread (4008) (.NET IO ThreadPool Worker)
- int firstIndex = threadFrame.IndexOf(')');
- int threadId = int.Parse(threadFrame.AsSpan(template.Length, firstIndex - template.Length));
+ // we are looking for the first index of ) because
+ // we need to handle a thread name like: Thread (4008) (.NET IO ThreadPool Worker)
+ int firstIndex = threadFrame.IndexOf(')');
+ int threadId = int.Parse(threadFrame.AsSpan(template.Length, firstIndex - template.Length));
- if (samplesForThread.TryGetValue(threadId, out List samples))
- {
- samples.Add(sample);
- }
- else
- {
- samplesForThread[threadId] = new List() { sample };
- }
- });
+ if (samplesForThread.TryGetValue(threadId, out List samples))
+ {
+ samples.Add(sample);
+ }
+ else
+ {
+ samplesForThread[threadId] = new List() { sample };
+ }
+ });
- // For every thread recorded in our trace, print the first stack
- foreach ((int threadId, List samples) in samplesForThread)
- {
+ // For every thread recorded in our trace, print the first stack
+ foreach ((int threadId, List samples) in samplesForThread)
+ {
#if DEBUG
- stdOutput.WriteLine($"Found {samples.Count} stacks for thread 0x{threadId:X}");
+ stdOutput.WriteLine($"Found {samples.Count} stacks for thread 0x{threadId:X}");
#endif
- PrintStack(stdOutput, threadId, samples[0], stackSource);
+ PrintStack(stdOutput, threadId, samples[0], stackSource);
+ }
}
}
}
From ea77b815c313cef1471b6c286b31b45413aad05d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 9 Oct 2025 02:20:39 +0000
Subject: [PATCH 07/11] Include ReversedServerHelpers.cs in dotnet-stack.csproj
Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com>
---
src/Tools/dotnet-stack/dotnet-stack.csproj | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Tools/dotnet-stack/dotnet-stack.csproj b/src/Tools/dotnet-stack/dotnet-stack.csproj
index beacb323f4..2c98d70f15 100644
--- a/src/Tools/dotnet-stack/dotnet-stack.csproj
+++ b/src/Tools/dotnet-stack/dotnet-stack.csproj
@@ -23,6 +23,7 @@
+
From 87df662f7d474dd2049b03894973410ac80f8912 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 9 Oct 2025 10:50:03 +0000
Subject: [PATCH 08/11] Add CommandLineErrorException.cs to dotnet-stack.csproj
Co-authored-by: steveisok <471438+steveisok@users.noreply.github.com>
---
src/Tools/dotnet-stack/dotnet-stack.csproj | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Tools/dotnet-stack/dotnet-stack.csproj b/src/Tools/dotnet-stack/dotnet-stack.csproj
index 2c98d70f15..9f0448817a 100644
--- a/src/Tools/dotnet-stack/dotnet-stack.csproj
+++ b/src/Tools/dotnet-stack/dotnet-stack.csproj
@@ -26,6 +26,7 @@
+
From 3a3a765f7a616548d374fca754b856499efd77a3 Mon Sep 17 00:00:00 2001
From: Steve Pfister
Date: Wed, 15 Oct 2025 15:19:22 -0400
Subject: [PATCH 09/11] Added a resume command and a little more clear error
message
---
src/Tools/dotnet-stack/ReportCommand.cs | 59 +++++++++++++++++++------
1 file changed, 46 insertions(+), 13 deletions(-)
diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs
index c7fd982112..a1c329bc78 100644
--- a/src/Tools/dotnet-stack/ReportCommand.cs
+++ b/src/Tools/dotnet-stack/ReportCommand.cs
@@ -88,6 +88,18 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput
DiagnosticsClient client = holder.Client;
+ // Resume runtime if it was suspended (similar to --resume-runtime:true in other tools)
+ // This is safe to call even if the runtime wasn't suspended - it's a no-op in that case
+ try
+ {
+ await client.ResumeRuntimeAsync(ct).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ // ResumeRuntime is a no-op if the runtime wasn't suspended,
+ // so we can safely ignore exceptions here
+ }
+
List providers = new()
{
new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational)
@@ -98,22 +110,40 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput
// is too short in a given environment, e.g., resource constrained systems
// N.B. - This trace INCLUDES rundown. For sufficiently large applications, it may take non-trivial time to collect
// the symbol data in rundown.
- EventPipeSession session = await client.StartEventPipeSessionAsync(providers, requestRundown:true, token:ct).ConfigureAwait(false);
- using (session)
- using (FileStream fs = File.OpenWrite(tempNetTraceFilename))
+ EventPipeSession session;
+ try
+ {
+ session = await client.StartEventPipeSessionAsync(providers, requestRundown:true, token:ct).ConfigureAwait(false);
+ }
+ catch (Exception)
+ {
+ stdError.WriteLine(EventPipeErrorMessage);
+ return -1;
+ }
+
+ try
{
- Task copyTask = session.EventStream.CopyToAsync(fs, ct);
- await Task.Delay(duration, ct).ConfigureAwait(false);
- await session.StopAsync(ct).ConfigureAwait(false);
-
- // check if rundown is taking more than 5 seconds and add comment to report
- Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(5));
- Task completedTask = await Task.WhenAny(copyTask, timeoutTask).ConfigureAwait(false);
- if (completedTask == timeoutTask)
+ using (session)
+ using (FileStream fs = File.OpenWrite(tempNetTraceFilename))
{
- stdOutput.WriteLine($"# Sufficiently large applications can cause this reportCommand to take non-trivial amounts of time");
+ Task copyTask = session.EventStream.CopyToAsync(fs, ct);
+ await Task.Delay(duration, ct).ConfigureAwait(false);
+ await session.StopAsync(ct).ConfigureAwait(false);
+
+ // check if rundown is taking more than 5 seconds and add comment to report
+ Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(5));
+ Task completedTask = await Task.WhenAny(copyTask, timeoutTask).ConfigureAwait(false);
+ if (completedTask == timeoutTask)
+ {
+ stdOutput.WriteLine($"# Sufficiently large applications can cause this reportCommand to take non-trivial amounts of time");
+ }
+ await copyTask.ConfigureAwait(false);
}
- await copyTask.ConfigureAwait(false);
+ }
+ catch (Exception)
+ {
+ stdError.WriteLine(EventPipeErrorMessage);
+ return -1;
}
// using the generated trace file, symbolicate and compute stacks.
@@ -255,5 +285,8 @@ public static Command ReportCommand()
{
Description = "The path to a diagnostic port to be used."
};
+
+ private const string EventPipeErrorMessage =
+ "There was a failure in reading stack data. Possible reasons could be trying to connect to a runtime that has been suspended, an unexpected close of the IPC channel, etc.";
}
}
From 7e8ef6b4f56e576bf852835a71040497369dacf0 Mon Sep 17 00:00:00 2001
From: Steve Pfister
Date: Wed, 15 Oct 2025 15:35:26 -0400
Subject: [PATCH 10/11] Silly me
---
src/Tools/dotnet-stack/ReportCommand.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs
index a1c329bc78..79389c4009 100644
--- a/src/Tools/dotnet-stack/ReportCommand.cs
+++ b/src/Tools/dotnet-stack/ReportCommand.cs
@@ -94,7 +94,7 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput
{
await client.ResumeRuntimeAsync(ct).ConfigureAwait(false);
}
- catch (Exception ex)
+ catch (Exception)
{
// ResumeRuntime is a no-op if the runtime wasn't suspended,
// so we can safely ignore exceptions here
From 79364ff7dad7125c95119aac23c09a6896ad0f92 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 21 Oct 2025 23:47:40 +0000
Subject: [PATCH 11/11] Follow dotnet-trace pattern for exception handling
- Changed ResumeRuntimeAsync to only catch UnsupportedCommandException per dotnet-trace pattern
- Removed overly broad try-catch blocks around EventPipeSession operations
- Let exceptions bubble up with full stack traces for better diagnostics
- Removed unused EventPipeErrorMessage constant
Co-authored-by: noahfalk <6243776+noahfalk@users.noreply.github.com>
---
src/Tools/dotnet-stack/ReportCommand.cs | 53 +++++++------------------
1 file changed, 15 insertions(+), 38 deletions(-)
diff --git a/src/Tools/dotnet-stack/ReportCommand.cs b/src/Tools/dotnet-stack/ReportCommand.cs
index 79389c4009..daaa91bb89 100644
--- a/src/Tools/dotnet-stack/ReportCommand.cs
+++ b/src/Tools/dotnet-stack/ReportCommand.cs
@@ -89,15 +89,13 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput
DiagnosticsClient client = holder.Client;
// Resume runtime if it was suspended (similar to --resume-runtime:true in other tools)
- // This is safe to call even if the runtime wasn't suspended - it's a no-op in that case
try
{
await client.ResumeRuntimeAsync(ct).ConfigureAwait(false);
}
- catch (Exception)
+ catch (UnsupportedCommandException)
{
- // ResumeRuntime is a no-op if the runtime wasn't suspended,
- // so we can safely ignore exceptions here
+ // Noop if command is unsupported, since the target is most likely a 3.1 app.
}
List providers = new()
@@ -110,40 +108,22 @@ private static async Task Report(CancellationToken ct, TextWriter stdOutput
// is too short in a given environment, e.g., resource constrained systems
// N.B. - This trace INCLUDES rundown. For sufficiently large applications, it may take non-trivial time to collect
// the symbol data in rundown.
- EventPipeSession session;
- try
- {
- session = await client.StartEventPipeSessionAsync(providers, requestRundown:true, token:ct).ConfigureAwait(false);
- }
- catch (Exception)
- {
- stdError.WriteLine(EventPipeErrorMessage);
- return -1;
- }
-
- try
+ EventPipeSession session = await client.StartEventPipeSessionAsync(providers, requestRundown:true, token:ct).ConfigureAwait(false);
+ using (session)
+ using (FileStream fs = File.OpenWrite(tempNetTraceFilename))
{
- using (session)
- using (FileStream fs = File.OpenWrite(tempNetTraceFilename))
+ Task copyTask = session.EventStream.CopyToAsync(fs, ct);
+ await Task.Delay(duration, ct).ConfigureAwait(false);
+ await session.StopAsync(ct).ConfigureAwait(false);
+
+ // check if rundown is taking more than 5 seconds and add comment to report
+ Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(5));
+ Task completedTask = await Task.WhenAny(copyTask, timeoutTask).ConfigureAwait(false);
+ if (completedTask == timeoutTask)
{
- Task copyTask = session.EventStream.CopyToAsync(fs, ct);
- await Task.Delay(duration, ct).ConfigureAwait(false);
- await session.StopAsync(ct).ConfigureAwait(false);
-
- // check if rundown is taking more than 5 seconds and add comment to report
- Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(5));
- Task completedTask = await Task.WhenAny(copyTask, timeoutTask).ConfigureAwait(false);
- if (completedTask == timeoutTask)
- {
- stdOutput.WriteLine($"# Sufficiently large applications can cause this reportCommand to take non-trivial amounts of time");
- }
- await copyTask.ConfigureAwait(false);
+ stdOutput.WriteLine($"# Sufficiently large applications can cause this reportCommand to take non-trivial amounts of time");
}
- }
- catch (Exception)
- {
- stdError.WriteLine(EventPipeErrorMessage);
- return -1;
+ await copyTask.ConfigureAwait(false);
}
// using the generated trace file, symbolicate and compute stacks.
@@ -285,8 +265,5 @@ public static Command ReportCommand()
{
Description = "The path to a diagnostic port to be used."
};
-
- private const string EventPipeErrorMessage =
- "There was a failure in reading stack data. Possible reasons could be trying to connect to a runtime that has been suspended, an unexpected close of the IPC channel, etc.";
}
}