[Mono.Android] Fix UnhandledExceptionRaiser not firing in .NET 10 - #10966
Merged
Conversation
Context: dotnet/java-interop#1275 Fixes: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2842209 dotnet/java-interop#1275 eliminated `JNINativeWrapper.CreateDelegate()` from generated binding marshal methods. The old code path routed exceptions through `AndroidEnvironment.UnhandledException()`, which fires the `UnhandledExceptionRaiser` event. The new code path calls `JniRuntime.OnUserUnhandledException()`, which only calls `SetPendingException()` and never invokes `AndroidEnvironment.UnhandledException()`. Fix by overriding `OnUserUnhandledException()` in `AndroidRuntime` to call `AndroidEnvironment.TryRaiseUnhandledException()` before delegating to the base implementation. If a subscriber sets `Handled = true`, the exception is swallowed and not transitioned to JNI. Refactor `AndroidEnvironment.UnhandledException()` to extract the event- raising logic into `TryRaiseUnhandledException()` so it can be called from both the old and new code paths. ~~ Tests ~~ Add a test that verifies both `UnhandledExceptionRaiser` and `AppDomain.UnhandledException` fire when an unhandled exception is thrown from a button click handler. The test: 1. Registers both `UnhandledExceptionRaiser` (with `e.Handled = true`) and `AppDomain.UnhandledException` handlers 2. Throws from the button click delegate 3. Asserts `UnhandledExceptionRaiser` fires via logcat Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a regression where managed exceptions thrown from generated binding marshal methods no longer trigger AndroidEnvironment.UnhandledExceptionRaiser on .NET 10+ (after the switch away from JNINativeWrapper.CreateDelegate()), by routing the new JniRuntime.OnUserUnhandledException() path through the same event-raising mechanism.
Changes:
- Override
AndroidRuntime.OnUserUnhandledException()to invokeAndroidEnvironment.TryRaiseUnhandledException()and swallow the exception whenHandled=true. - Refactor
AndroidEnvironment.UnhandledException()to extract reusable event-raising logic intoTryRaiseUnhandledException(). - Add a device integration test that throws from a button click handler and validates logcat output for
UnhandledExceptionRaiser.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs | Adds a device test that throws from a UI click handler and checks logcat for the raised unhandled-exception event. |
| src/Mono.Android/Android.Runtime/AndroidRuntime.cs | Overrides OnUserUnhandledException() to raise UnhandledExceptionRaiser for the new Java.Interop exception path. |
| src/Mono.Android/Android.Runtime/AndroidEnvironment.cs | Extracts event-raising into TryRaiseUnhandledException() so both legacy and new paths can share it. |
jonathanpeppers
requested review from
grendello and
simonrozsival
as code owners
March 18, 2026 16:22
- Assert WaitForActivityToStart return value - Remove unused AppDomain.UnhandledException handler (it won't fire when UnhandledExceptionRaiser sets Handled = true) - Fix misleading comment in OnUserUnhandledException override Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jonathanpeppers
force-pushed
the
dev/peppers/unhandledexceptions
branch
from
March 18, 2026 16:23
c61e4b7 to
966b26f
Compare
simonrozsival
approved these changes
Mar 18, 2026
grendello
approved these changes
Mar 19, 2026
jonathanpeppers
added a commit
that referenced
this pull request
Mar 19, 2026
…0966) Context: dotnet/java-interop#1275 Fixes: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2842209 dotnet/java-interop#1275 eliminated `JNINativeWrapper.CreateDelegate()` from generated binding marshal methods. The old code path routed exceptions through `AndroidEnvironment.UnhandledException()`, which fires the `UnhandledExceptionRaiser` event. The new code path calls `JniRuntime.OnUserUnhandledException()`, which only calls `SetPendingException()` and never invokes `AndroidEnvironment.UnhandledException()`. Fix by overriding `OnUserUnhandledException()` in `AndroidRuntime` to call `AndroidEnvironment.TryRaiseUnhandledException()` before delegating to the base implementation. If a subscriber sets `Handled = true`, the exception is swallowed and not transitioned to JNI. Refactor `AndroidEnvironment.UnhandledException()` to extract the event- raising logic into `TryRaiseUnhandledException()` so it can be called from both the old and new code paths. ~~ Tests ~~ Add a test that verifies both `UnhandledExceptionRaiser` and `AppDomain.UnhandledException` fire when an unhandled exception is thrown from a button click handler. The test: 1. Registers both `UnhandledExceptionRaiser` (with `e.Handled = true`) and `AppDomain.UnhandledException` handlers 2. Throws from the button click delegate 3. Asserts `UnhandledExceptionRaiser` fires via logcat
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context: dotnet/java-interop#1275
Fixes: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2842209
dotnet/java-interop#1275 eliminated
JNINativeWrapper.CreateDelegate()from generated binding marshal methods. The old code path routed exceptions throughAndroidEnvironment.UnhandledException(), which fires theUnhandledExceptionRaiserevent. The new code path callsJniRuntime.OnUserUnhandledException(), which only callsSetPendingException()and never invokesAndroidEnvironment.UnhandledException().Fix by overriding
OnUserUnhandledException()inAndroidRuntimeto callAndroidEnvironment.TryRaiseUnhandledException()before delegating to the base implementation. If a subscriber setsHandled = true, the exception is swallowed and not transitioned to JNI.Refactor
AndroidEnvironment.UnhandledException()to extract the event- raising logic intoTryRaiseUnhandledException()so it can be called from both the old and new code paths.Tests
Add a test that verifies both
UnhandledExceptionRaiserandAppDomain.UnhandledExceptionfire when an unhandled exception is thrown from a button click handler.The test:
UnhandledExceptionRaiser(withe.Handled = true) andAppDomain.UnhandledExceptionhandlersUnhandledExceptionRaiserfires via logcat