dotnet / dotnet/runtime

[API Proposal]: Include runtime async frames in Environment.StackTrace and System.Diagnostics.StackFrame

Open
#125,417 20 comments 4 reactions 1 assignee Claimed by @tommcdon View on GitHub
area-Diagnostics-coreclr enhancement runtime-async
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Background

Customers often use logging to diagnose issues in production code. Environment.StackTrace is one of the possible ways to log current callstack and method location. Async v1 code often times be undiagnosable due to thread switches, for example calling Environment.StackTrace immediately after `await Task.Delay` would be resumed on a new thread, and so there would be no indication on how execution led to the current method.

Runtime Async (also known as Async V2) gives us a new opportunity to improve the scenario. Previously in Async V1, async execution state was tracked in Roslyn compiler-generated code, requiring decoding of async state machines to track caller/callee chains. Now with Async V2, the runtime tracks async execution and so we can now extract the resume locations and output this information as part of a synchronous callstack.

### Motivation

### Current behavior (without this change)

When a runtime async v2 method yields and is later resumed by the thread pool, `Environment.StackTrace` shows only what is physically on the stack:

```
at System.Environment.get_StackTrace()
at MyApp.MiddleMethod()
at System.Runtime.CompilerServices.YieldAwaitable+YieldAwaiter.RunAction(Object)
at System.Threading.QueueUserWorkItemCallbackDefaultContext.Execute()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading.PortableThreadPool+WorkerThread.WorkerDoWork(...)
at System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart()
at System.Threading.Thread.StartCallback()
```

The logical callers — which async methods are awaiting `MiddleMethod` — are completely absent. A developer has no way to determine *why* `MiddleMethod` is running.

### Desired behavior

```
at System.Environment.get_StackTrace()
at MyApp.MiddleMethod() in MyApp.cs:line 42
at MyApp.OuterMethod() in MyApp.cs:line 30 ← continuation frame
at MyApp.EntryPoint() in MyApp.cs:line 15 ← continuation frame
```

Internal dispatch machinery (`DispatchContinuations`, thread pool frames) is hidden, and the logical async caller chain is reconstructed from the runtime's continuation data.

### Why this matters

- Debugging: Log files with `Environment.StackTrace` are a useful diagnostic tool. Without the async caller chain, developers cannot trace the origin of a call.
- Parity with Exception stack traces
- Observability tooling: APM tools, logging frameworks, and profilers that consume `StackTrace` objects need the logical call chain to build meaningful traces.

### API Proposal

No API changes are suggested - this is changing behavior of an existing API to provide more information. API's affected are:
* System.Diagnostics.StackFrame
* System.Diagnostics.StackTrace.ctor
* System.Environment.StackTrace

Draft PR with proposed changes is here: https://github.com/dotnet/runtime/pull/125396

### API Usage

```csharp
static void PrintStackTrace()
{
// Get the current stack trace as a string
string stackTrace = Environment.StackTrace;

Console.WriteLine("Current Stack Trace:");
Console.WriteLine(stackTrace);
}
```

### Alternative Designs

We could consider adding overloads that make the behavior opt-in or opt-out

### Risks

Including runtime async frames might unexpectedly slow down performance for those that log callstacks in production scenarios, or bloat the log files themselves causing an extra expense on log storage.
Risk is mitigated by:
1. Runtime async is currently opt-in at the application level (at the time of writing in .NET 11)
2. Diagnostic benefits should outweigh performance or log size concerns. To mitigate this risk, the feature should include an opt-out app config switch to revert to fully non-async callstacks. We can also consider API overloads as well depending on feedback.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.