Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 29 additions & 7 deletions src/libraries/System.Private.CoreLib/src/System/AppContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,21 +130,43 @@ internal static void OnUnhandledException(object e)
}
}

[ThreadStatic]
private static bool t_deliveringFirstChanceNotification;

private static void OnFirstChanceException(Exception e, object? sender)
{
if (FirstChanceException is EventHandler<FirstChanceExceptionEventArgs> handlers)
{
FirstChanceExceptionEventArgs args = new(e);
foreach (EventHandler<FirstChanceExceptionEventArgs> handler in Delegate.EnumerateInvocationList(handlers))
// Guard against reentrancy. Allocating the event args below or running a
// handler may itself throw (e.g. OutOfMemoryException in a low-memory
// situation). That exception would trigger another first-chance
// notification on this same thread, allocate again, throw again, and
// recurse until the stack overflows. Skip nested notifications to break
// the recursion.
if (t_deliveringFirstChanceNotification)
{
try
{
handler(sender, args);
}
catch
return;
}

t_deliveringFirstChanceNotification = true;
try
{
FirstChanceExceptionEventArgs args = new(e);
foreach (EventHandler<FirstChanceExceptionEventArgs> handler in Delegate.EnumerateInvocationList(handlers))
{
try
{
handler(sender, args);
}
catch
{
}
}
}
finally
{
t_deliveringFirstChanceNotification = false;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,33 @@ public void FirstChanceException_Called()
}).Dispose();
}

[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public void FirstChanceException_HandlerThrows_DoesNotRecurse()
{
// A handler that throws must not cause the runtime to recursively deliver
// first-chance notifications for the exceptions it throws, which would
// otherwise recurse until the stack overflows. The handler should be
// invoked exactly once for the original exception.
RemoteExecutor.Invoke(() => {
int count = 0;
EventHandler<FirstChanceExceptionEventArgs> handler = (sender, e) =>
{
count++;
throw new FirstChanceTestException("from handler");
};
AppDomain.CurrentDomain.FirstChanceException += handler;
try
{
throw new FirstChanceTestException("outer");
}
catch
{
}
AppDomain.CurrentDomain.FirstChanceException -= handler;
Assert.Equal(1, count);
}).Dispose();
}

class FirstChanceTestException : Exception
{
public FirstChanceTestException(string message) : base(message)
Expand Down
Loading