Repository navigation
[NativeAOT] "Fix" AndroidRuntimeInternal.WaitForBridgeProcessing() - #10128
Merged
jonpryor merged 1 commit intoMay 10, 2025
Merged
Conversation
Fixes: #10126 Context: 4b158ce Context: dotnet/java-interop@356485e Context: 32cff43 NativeAOT currently requires new binding assemblies, bindings which do not use `JNINativeWrapper.CreateDelegate()`, as one of the code paths within `JNINativeWrapper.CreateDelegate()` uses `System.Reflection.Emit`, which does not exist in NativeAOT. dotnet/java-interop@356485ee contains the `generator` changes needed to avoid `JNINativeWrapper.CreateDelegate()`. However, there is one situation in which that "requirement" is (kinda) *conditional*: commit 32cff43 added "builtin" JNI wrapper methods, avoiding the need for System.Reflection.Emit when overriding Java methods that match a builtin signature. This means existing binding assemblies can work, right? Kinda, yeah! So long as you *only* override methods which match a builtin signature. Which was enough to get MAUI going atop NativeAOT *without* rebuilding all of dotnet/android-libraries⦠However, for not-entirely-understood reasons, sometimes using a builtin JNI wrapper method would cause the app to crash: digest=============com.avalonia.safeareademo /apex/com.android.runtime/lib/bionic/libc.so (abort+) () XXlib/arm/libSafeAreaDemo.Android.so (SystemNative_Abort+) () XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_Interop_Sys__Abort+) () XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_System_RuntimeExceptionHelpers__FailFast+) () XXlib/arm/libSafeAreaDemo.Android.so (RuntimeFailFast+) () XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_System_Runtime_EH__UnhandledExceptionFailFastViaClasslib+) () XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_System_Runtime_EH__DispatchEx+) () XXlib/arm/libSafeAreaDemo.Android.so (RhThrowEx+) () XXlib/arm/libSafeAreaDemo.Android.so (RhpThrowEx+) () XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_System_Runtime_CompilerServices_ClassConstructorRunner__EnsureClassConstructorRun+) () XXlib/arm/libSafeAreaDemo.Android.so (RhpCallCatchFunclet+) () XXlib/arm/libSafeAreaDemo.Android.so (S_P_CoreLib_System_Runtime_CompilerServices_ClassConstructorRunner__CheckStaticClassConstructionReturnNonGCStaticBase+) () XXlib/arm/libSafeAreaDemo.Android.so (Mono_Android_Runtime_Android_Runtime_AndroidRuntimeInternal__WaitForBridgeProcessing+) () XXlib/arm/libSafeAreaDemo.Android.so (Mono_Android_Android_Runtime_JNINativeWrapper__Wrap_JniMarshal_PPLL_L+) () XXlib/arm/libSafeAreaDemo.Android.so (Mono_Android__JniMarshal_PPLL_L__InvokeClosedStaticThunk+) () XXlib/arm/libSafeAreaDemo.Android.so (Internal_CompilerGenerated__Module___<ReverseDelegateStub>Mono_Android__JniMarshal_PPLL_L+) () /apex/com.android.art/lib/libart.so (art_quick_generic_jni_trampoline+) () The cause of this crash is that `JNIINativeWrapper.Wrap_JniMarshal_PPLL_L()` called `AndroidRuntimeInternal.WaitForBridgeProcessing()`, which in turn triggers the `AndroidRuntimeInternal` static constructor, which *throws* unless on MonoVM or CoreCLR, of which NativeAOT is neither. Fix this by updating `JNINativeWrapper.g.tt` to call `JNIEnv.ValueManager?.WaitForGCBridgeProcessing()` instead of `AndroidRuntimeInternal.WaitForBridgeProcessing()`. This allows NativeAOT to control what actually happens. Aside: this project includes [T4 Text Templates][0]. To regenerate the output files *without involving Visual Studio*, you can install the [`dotnet-t4`][1] tool: $ dotnet tool install --global dotnet-t4 then run it separately for each `.tt` file: $HOME/.dotnet/tools/t4 -o src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs \ src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt [0]: https://learn.microsoft.com/visualstudio/modeling/code-generation-and-t4-text-templates?view=vs-2022 [1]: https://www.nuget.org/packages/dotnet-t4/
Contributor
There was a problem hiding this comment.
Pull Request Overview
This PR fixes a crash on NativeAOT by replacing calls to AndroidRuntimeInternal.WaitForBridgeProcessing() with JNIEnvInit.ValueManager?.WaitForGCBridgeProcessing(), ensuring that the proper bridge processing method is invoked for built-in JNI wrappers.
- Updated code in T4 template (JNINativeWrapper.g.tt)
- Updated multiple JNI wrapper methods in JNINativeWrapper.g.cs
- Modified JNIEnv.WaitForBridgeProcessing() to use the new API
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt | Replaced wait call to use JNIEnvInit.ValueManager?.WaitForGCBridgeProcessing() |
| src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs | Updated all JNI wrapper methods to call JNIEnvInit.ValueManager?.WaitForGCBridgeProcessing() |
| src/Mono.Android/Android.Runtime/JNIEnv.cs | Modified WaitForBridgeProcessing() to use the new call for NativeAOT support |
Comments suppressed due to low confidence (3)
src/Mono.Android/Android.Runtime/JNINativeWrapper.g.tt:271
- Ensure that JNIEnvInit.ValueManager is always properly initialized in contexts where WaitForGCBridgeProcessing() is required, as using the null-conditional operator may suppress necessary processing if ValueManager is null. Consider adding inline documentation to clarify this behavior.
JNIEnvInit.ValueManager?.WaitForGCBridgeProcessing ();
src/Mono.Android/Android.Runtime/JNINativeWrapper.g.cs:20
- Verify that the substitution of AndroidRuntimeInternal.WaitForBridgeProcessing() with JNIEnvInit.ValueManager?.WaitForGCBridgeProcessing() across all JNI wrapper methods meets the intended behavior in all runtime scenarios, particularly in NativeAOT where proper bridge processing is critical.
JNIEnvInit.ValueManager?.WaitForGCBridgeProcessing ();
src/Mono.Android/Android.Runtime/JNIEnv.cs:121
- Confirm that replacing the direct call with the null-conditional access does not unintentionally bypass necessary wait logic when ValueManager might be null. Inline comments or documentation could help explain this behavior for future maintainers.
JNIEnvInit.ValueManager?.WaitForGCBridgeProcessing ();
jonathanpeppers
approved these changes
May 9, 2025
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.
Fixes: #10126
Context: 4b158ce
Context: dotnet/java-interop@356485e
Context: 32cff43
NativeAOT currently requires new binding assemblies, bindings which do not use
JNINativeWrapper.CreateDelegate(), as one of the code paths withinJNINativeWrapper.CreateDelegate()usesSystem.Reflection.Emit, which does not exist in NativeAOT. dotnet/java-interop@356485ee contains thegeneratorchanges needed to avoidJNINativeWrapper.CreateDelegate().However, there is one situation in which that "requirement" is (kinda) conditional: commit 32cff43 added "builtin" JNI wrapper methods, avoiding the need for System.Reflection.Emit when overriding Java methods that match a builtin signature.
This means existing binding assemblies can work, right?
Kinda, yeah! So long as you only override methods which match a builtin signature. Which was enough to get MAUI going atop NativeAOT without rebuilding all of dotnet/android-librariesβ¦
However, for not-entirely-understood reasons, sometimes using a builtin JNI wrapper method would cause the app to crash:
The cause of this crash is that
JNIINativeWrapper.Wrap_JniMarshal_PPLL_L()calledAndroidRuntimeInternal.WaitForBridgeProcessing(), which in turn triggers theAndroidRuntimeInternalstatic constructor, which throws unless on MonoVM or CoreCLR, of which NativeAOT is neither.Fix this by updating
JNINativeWrapper.g.ttto callJNIEnv.ValueManager?.WaitForGCBridgeProcessing()instead ofAndroidRuntimeInternal.WaitForBridgeProcessing(). This allows NativeAOT to control what actually happens.Aside: this project includes T4 Text Templates. To regenerate the output files without involving Visual Studio, you can install the
dotnet-t4tool:then run it separately for each
.ttfile: