You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[API Proposal]: Let accessing the instance of Exception (if any) on stack. #89724
However, if I don't want `OperationCancelledException' to be treated as an error, I need to have access to the instance of the exception thrown. Currently, there's no such possibility.
API Proposal
namespace System.Runtime.ExceptionServices
{
public static class ExceptionHandling
{
public Exception? CurrentException {get;}
}
}
This API is managed equivalent of the existing ICorDebugThread::GetCurrentException unmanaged debugger API. throw should set this per-thread property (saving previous value), catch with matching exception should restore the previous value.
API Usage
try{thrownewException();}finally{// Here it is not null;Console.WriteLine(AppDomain.CurrentDomain.CurrentExceptionis not null);}
try{thrownewException();}catch{// Here it is null;Console.WriteLine(AppDomain.CurrentDomain.CurrentExceptionisnull);}finally{// Here it is null;Console.WriteLine(AppDomain.CurrentDomain.CurrentExceptionisnull);}
try{thrownewException();}catch{// Here it is null;Console.WriteLine(AppDomain.CurrentDomain.CurrentExceptionisnull);throw;}finally{// Here it is not null;Console.WriteLine(AppDomain.CurrentDomain.CurrentExceptionis not null);}
Not sure whether value should propagate with async.
TaskCheckMe()=>Task.Run(()=>Console.WriteLine(AppDomain.CurrentDomain.CurrentExceptionis not null));try{thrownewException();}finally{// Here it is not null;Console.WriteLine(AppDomain.CurrentDomain.CurrentExceptionis not null);// will it output true or false? awaitCheckMe();// Here it is not null;Console.WriteLine(AppDomain.CurrentDomain.CurrentExceptionis not null);}
Could Open Telemetry achieve the same result using a first-chance exception handler?
Whenever an exception is thrown, it could be stored in a thread/task -local field. The ExceptionProcessor could then access the most recent exception using the field and determine whether that exception is bubbling up using fnGetExceptionPointers.
This will leak the GCHandles over time as threads come and go. There is nothing that would take care of cleaning up of the GCHandles - GCHandles are low-level "unmanaged" resources that must be freed manually.
I ended up with the implementation below. Do you see any possible issues with it?
This will capture the last thrown exception. It won't be the exception that the activity crashed with if there are exceptions thrown and handled during cleanup (finally blocks, using statements in C#, etc.). There is no good way to this 100% right today. It is what this new API proposal is about - introduce an API that allow you to do it properly.
catch
{
// Here it is null;
Console.WriteLine(AppDomain.CurrentDomain.CurrentException is null);
@voroninp Returning null inside the catch block is different from how other similar methods (like ICorDebugThread::GetCurrentException you mentioned) behave. Is this detail important to your use case, or do you think it would be reasonable to return the exception being handled inside the catch block too?
@jkotas, here are some measurements from our review of #134883 that bear on this question. To check that the tests cover each of the PR's guards, we built 68cf753 without the store that sets CatchHandlerRunningFlag in CallCatchFunclet. On CoreCLR, that build returns the newest exception on the thread that has not been superseded, including one a catch is currently handling, which is the alternative you describe. We ran our probe program on both builds (CoreCLR linux-x64 Checked).
21 of the probe's 71 observation points change. All 21 run while a catch handler is executing: in the catch itself, in a finally or using that completes normally inside it, or in a callee. A separate comparison with Marshal.GetExceptionPointers(), at four further points, is described below.
Inside a catch, a finally that a throw; or a new exception passes through does not change. Neither do the filter and fault observations made outside a catch, nor an outer finally reached after a return leaves the catch: that finally returns null in both builds.
Nested exceptions stay ordered. With the mutation, inside the catch of A, a catch of a nested exception B returns B, and once that inner catch finishes the outer catch returns A again. In both builds, a finally that B unwinds through inside the catch of A returns B.
windows-x64: the same mutation turns the same 13 of 22 tests red as on Linux. The 22 are the PR's test class plus the tests we proposed in the review. We did not run the probe program on the Windows mutant.
What follows from those observations:
Marshal.GetExceptionPointers(). We compared four points: outside any handler, in a filter, in a finally and in a catch. On both Linux and Windows, the PR's null/non-null result differs from its zero/non-zero result only in the catch. With the mutation, all four agree on Linux. We did not compare pointer identity with exception identity.
Code that runs inside a catch, with the mutation, can get the handled exception without the catch passing it in.
Normal completion versus rethrow inside a catch. This matters to scope-exit observers like the telemetry and using (var txn = ...) cases discussed above. Take our example, with no other exception in flight. Under the PR, a using that completes normally inside a catch sees null, while a finally around throw; in the same catch sees the caught exception. With the mutation, both see that exception, so a null check cannot tell them apart. Comparing against a reference taken when the scope starts cannot either, because a rethrow is the same object; that last point follows from the observations and was not probed separately. Separately, in the probes for normal completion during an unwind, a callee's finally and a using inside a finally unwinding A both see A under either rule.
If the rule changes, it would help for the remarks to say that code running inside a catch handler sees the handled exception even when that code completes normally.
The 21 observation points that change (CoreCLR linux-x64 Checked, based on 68cf753)
FinallyInsideCatch: callee finally called from catch
null
A
NestedCatchInsideCatch: inner catch
null
B
NestedCatchInsideCatch: after inner catch, still in outer catch
null
A
RethrowInsideTryInsideCatch: outer catch
null
A
ThrowNewFromCatch: catch of the new exception
null
B
ExceptionCaughtInsideFilter: filter callee: catch of B
A
B
ExceptionCaughtInsideFilter: handler selected by that filter
null
A
FilterThrowsUncaught: outer handler
null
A
FilterThenSecondPassFinally: handler
null
A
EdiThrowFromCatch: catch
null
A
EdiThrowFromFinally: catch
null
B
SecondPassFinallyInCallerFrames: catch two frames up
null
A
SupersededWithFrameBetween: catch of B (A superseded)
null
B
ReversePInvokeDuringFinally: callback with nothing in flight: catch
null
B
ReversePInvokeDuringFinally: callback during finally of A: catch of B
A
B
A and B are the exceptions each probe throws, in order.
Note
This comment was drafted with AI assistance (Anthropic Claude, reviewed by OpenAI Codex). The probe runs it cites used builds based on commit 68cf753, including the catch-flag mutation described above.
@jkotas , in my mental model catch "removes" exception from the context unless it's explicitly rethrown. But as I can imagine it's not critical for my case.
Activity
grbell-ms commentedon Aug 2, 2023
Could Open Telemetry achieve the same result using a first-chance exception handler?
Whenever an exception is thrown, it could be stored in a thread/task -local field. The
ExceptionProcessorcould then access the most recent exception using the field and determine whether that exception is bubbling up usingfnGetExceptionPointers.voroninp commentedon Aug 2, 2023
@grbell-ms
If I am not missing something particularly important, it should work.
grbell-ms commentedon Aug 2, 2023
@voroninp I would change☺️
GCHandletoWeakReference<Exception>for simplicity, but that's about what I was imagining.But I'm no expert, so no promises this works without extensive testing. 😅
voroninp commentedon Aug 2, 2023
I started with Weak reference, but decided, GCHandle will prevent memory allocations.
adrianotr commentedon May 4, 2024
I ended up with the implementation below. Do you see any possible issues with it?
voroninp commentedon May 4, 2024
Yep came up to similar one https://gist.github.com/voroninp/704c1cb556bd03697ef0c80524de1b24
jkotas commentedon May 4, 2024
This will leak the GCHandles over time as threads come and go. There is nothing that would take care of cleaning up of the GCHandles - GCHandles are low-level "unmanaged" resources that must be freed manually.
voroninp commentedon May 4, 2024
@jkotas , despite it allocation with weak type?!
jkotas commentedon May 4, 2024
This will capture the last thrown exception. It won't be the exception that the activity crashed with if there are exceptions thrown and handled during cleanup (finally blocks,
usingstatements in C#, etc.). There is no good way to this 100% right today. It is what this new API proposal is about - introduce an API that allow you to do it properly.21 remaining items
jkotas commentedon Jul 25, 2026
I have updated the proposal
bartonjs commentedon Sep 29, 2026
Video
jkotas commentedon Oct 10, 2026
@voroninp Returning
nullinside the catch block is different from how other similar methods (likeICorDebugThread::GetCurrentExceptionyou mentioned) behave. Is this detail important to your use case, or do you think it would be reasonable to return the exception being handled inside the catch block too?pcshrosbree commentedon Oct 10, 2026
@jkotas, here are some measurements from our review of #134883 that bear on this question. To check that the tests cover each of the PR's guards, we built 68cf753 without the store that sets
CatchHandlerRunningFlaginCallCatchFunclet. On CoreCLR, that build returns the newest exception on the thread that has not been superseded, including one a catch is currently handling, which is the alternative you describe. We ran our probe program on both builds (CoreCLR linux-x64 Checked).usingthat completes normally inside it, or in a callee. A separate comparison withMarshal.GetExceptionPointers(), at four further points, is described below.throw;or a new exception passes through does not change. Neither do the filter and fault observations made outside a catch, nor an outer finally reached after areturnleaves the catch: that finally returns null in both builds.What follows from those observations:
Marshal.GetExceptionPointers(). We compared four points: outside any handler, in a filter, in a finally and in a catch. On both Linux and Windows, the PR's null/non-null result differs from its zero/non-zero result only in the catch. With the mutation, all four agree on Linux. We did not compare pointer identity with exception identity.using (var txn = ...)cases discussed above. Take our example, with no other exception in flight. Under the PR, ausingthat completes normally inside a catch sees null, while a finally aroundthrow;in the same catch sees the caught exception. With the mutation, both see that exception, so a null check cannot tell them apart. Comparing against a reference taken when the scope starts cannot either, because a rethrow is the same object; that last point follows from the observations and was not probed separately. Separately, in the probes for normal completion during an unwind, a callee's finally and ausinginside a finally unwinding A both see A under either rule.If the rule changes, it would help for the remarks to say that code running inside a catch handler sees the handled exception even when that code completes normally.
The 21 observation points that change (CoreCLR linux-x64 Checked, based on 68cf753)
A and B are the exceptions each probe throws, in order.
Note
This comment was drafted with AI assistance (Anthropic Claude, reviewed by OpenAI Codex). The probe runs it cites used builds based on commit 68cf753, including the catch-flag mutation described above.
voroninp commentedon Oct 10, 2026
@jkotas , in my mental model
catch"removes" exception from the context unless it's explicitly rethrown. But as I can imagine it's not critical for my case.Add access to exception instance on stack (#134883)
voroninp commentedon Oct 13, 2026
@jkotas , is it going to be available in .NET 11 RC2?
jkotas commentedon Oct 13, 2026
This PR is for .NET 12. .NET 11 is feature complete for a while now.