Fail fast when FirstChanceExceptionEventArgs allocation fails#130505
Conversation
|
@copilot have the actual implementation got dropped from the changes somehow? |
Yes — the branch only had the empty "Initial plan" commit, so the implementation was missing. I've now pushed it. Note that since the original PR body's snippet was written, #124834 moved the So the fix moves the allocation back into the VM in EX_TRY
{
MethodTable *pMTEventArgs = CoreLibBinder::GetClass(CLASS__FIRSTCHANCE_EVENTARGS);
gc.oEventArgs = AllocateObject(pMTEventArgs);
MethodDescCallSite ctor(METHOD__FIRSTCHANCE_EVENTARGS__CTOR, &gc.oEventArgs);
ARG_SLOT ctorArgs[] = { ObjToArgSlot(gc.oEventArgs), ObjToArgSlot(gc.oThrowable) };
ctor.Call(ctorArgs);
}
EX_CATCH
{
EEPOLICY_HANDLE_FATAL_ERROR(COR_E_OUTOFMEMORY);
UNREACHABLE();
}
EX_END_CATCHThe pre-created event args is then passed to the managed handler-dispatch path (the CoreCLR Validated on a fresh clr+libs+host build: normal flow (handler called once with non-null args) and handler-throws (nested first-chance still delivered with valid args, no spurious fail-fast) both pass. Note This comment was generated by Copilot. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/libraries/System.Private.CoreLib/src/System/AppContext.cs:163
- The PR title/description says we should fail-fast when allocating
FirstChanceExceptionEventArgsfails, but this catch currently just returns and silently skips the notification. If fail-fast is the intended behavior, limit the catch toOutOfMemoryExceptionand callEnvironment.FailFast(...)so we don’t continue after an allocation failure.
catch
{
// If we fail to create the event args while delivering a first-chance
// notification, skip delivering this notification.
return;
src/libraries/System.Private.CoreLib/src/System/AppContext.cs:144
- The new reentrancy guard and per-handler
catch { }change behavior on the successful-allocation path when aFirstChanceExceptionhandler throws (nested first-chance notifications are now suppressed and the handler exception is swallowed so other handlers can run). The PR description currently claims there’s “no behavioral change on the normal successful-allocation path”; please update the PR description (or limit the change) so reviewers and release notes reflect the actual behavior change.
// 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
| try | ||
| { | ||
| FirstChanceExceptionEventArgs args; | ||
| try |
There was a problem hiding this comment.
@copilot This try/catch around new(e) should not be needed. The exception will be swallowed by the OnFirstChanceException caller. Keep it as FirstChanceExceptionEventArgs args = new(e); that it was before
There was a problem hiding this comment.
Removed the try/catch in b910344; it's now back to FirstChanceExceptionEventArgs args = new(e);.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/AppDomainTests.cs:211
- The test handler currently increments/throws for any first-chance exception in the RemoteExecutor process. Since first-chance notifications are process-wide (all threads), unrelated framework/runtime exceptions during the test window could make this flaky and also reduce signal.
Consider filtering to the specific exception type this test cares about (FirstChanceTestException) before incrementing/throwing, so the test only fails when recursion happens for that scenario.
EventHandler<FirstChanceExceptionEventArgs> handler = (sender, e) =>
{
count++;
throw new FirstChanceTestException("from handler");
};
src/libraries/System.Private.CoreLib/src/System/AppContext.cs:144
- The PR description/title say the runtime will fail-fast when
FirstChanceExceptionEventArgsallocation fails and will not invoke managed handlers with invalid state. This change instead introduces a per-thread reentrancy guard that silently skips nested first-chance notifications to avoid recursion/stack overflow.
Please reconcile the intended behavior: either update the PR description (and title) to match this "skip nested notifications" approach, or adjust the implementation to actually fail-fast specifically on event-args allocation failure as described.
// 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
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/AppDomainTests.cs:211
- This test handler throws for every first-chance notification after subscription, but the runtime can raise first-chance events for unrelated internal exceptions as well (see AppDomain.cs:86-90 comment about delivering for all exceptions). That makes
countpotentially > 1 and the test flaky. Consider throwing only whene.Exceptionis the specific test exception type so the test remains scoped to the behavior it is asserting.
RemoteExecutor.Invoke(() => {
int count = 0;
EventHandler<FirstChanceExceptionEventArgs> handler = (sender, e) =>
{
count++;
throw new FirstChanceTestException("from handler");
};
src/libraries/System.Private.CoreLib/src/System/AppContext.cs:149
- The PR description/title claims the runtime will fail-fast when
FirstChanceExceptionEventArgsallocation fails, but this change instead adds a managed reentrancy guard and (on CoreCLR) swallows exceptions from the callback path. As written, an allocation failure (OOM) would result in the first-chance notification being skipped, not a deterministic fail-fast. Please either update the PR description/title to match the actual behavior change, or implement the documented fail-fast behavior in the runtime dispatch path.
// 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)
{
return;
}
Allocate the FirstChanceExceptionEventArgs in the VM during first-chance dispatch and fail fast if the allocation fails, instead of allocating in managed code (which would recurse to a stack overflow on OOM) or passing a null event args to handlers. Co-authored-by: VSadov <8218165+VSadov@users.noreply.github.com>
… MethodDescCallSite Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
Co-authored-by: VSadov <8218165+VSadov@users.noreply.github.com>
Co-authored-by: VSadov <8218165+VSadov@users.noreply.github.com>
…ocation Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
c8f7da9 to
5f4fd08
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/libraries/System.Private.CoreLib/src/System/AppContext.cs:149
- The PR description/title says allocation failure of FirstChanceExceptionEventArgs should fail-fast (and implies the allocation happens in the VM), but this change instead adds a managed reentrancy guard that suppresses nested first-chance notifications and continues execution. If the intended behavior is now “skip nested notifications to avoid recursion”, the PR metadata should be updated; if the intended behavior is truly fail-fast on args allocation failure, this method would need an explicit fatal path (e.g., fail-fast on OutOfMemoryException from
new FirstChanceExceptionEventArgs(e)), which is not present here.
// 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)
{
return;
}
main PR N/A
Description
First-chance exception dispatch could invoke managed handlers with a null
FirstChanceExceptionEventArgswhen allocation failed, which can recurse and end in stack overflow. This change makes that allocation-failure path terminate via fail-fast instead of calling into managed handlers with invalid state.Behavior change
FirstChanceExceptionEventArgsfails, runtime now fail-fasts immediately.AppDomain.FirstChanceExceptionhandlers with a null event args instance.Implementation
FirstChanceExceptionEventArgsallocation failure as fatal.Customer Impact
Prevents unbounded recursion/stack overflow in low-memory exception paths and replaces it with deterministic fail-fast behavior.
Regression
Not identified as a recent regression; this addresses a longstanding edge case in an OOM path.
Testing
Covered by existing first-chance exception behavior tests for normal flow; this change is isolated to the fatal allocation-failure branch.
Risk
Low. Change is narrowly scoped to an already-fatal low-memory edge path and does not affect normal exception dispatch semantics.
Package authoring no longer needed in .NET 9
IMPORTANT: Starting with .NET 9, you no longer need to edit a NuGet package's csproj to enable building and bump the version.
Keep in mind that we still need package authoring in .NET 8 and older versions.