Skip to content

Commit 1f8f719

Browse files
simonrozsivaljonathanpeppersgrendello
authored
[CoreCLR] Implement GC bridge (#10198)
Replaces: #10185 Implements: dotnet/runtime#115506 Builds on top of: dotnet/runtime#116310 ### Description This PR implements GC bridge for CoreCLR using the `JavaMarshal` APIs introduced in dotnet/runtime#116310. The code in this PR is CoreCLR specific and while it will build with other runtimes on .NET 10, the `ManagedValueManager` class will throw on any other runtime other than CoreCLR. In the future, the same GC bridge mechanism should be also supported by Native AOT, so this code might be reused for that platform as well at some point. The code of the GC bridge is placed in 3 main locations: #### `ManagedValueManager.cs` Code in this class interfaces with the `JavaMarshal` APIs. This class keeps a dictionary of mapping between .NET and Java objects `RegisteredInstances`. The class carefuly manages the lifetimes of the bridge objects and their associated native memory ("GC bridge context" - `HandleContext`). The implementation follows these rules: * Do not access the `Target` of the reference tracking `GCHandles`. Doing this could cause a race condition with the GC collecting handles in background thread. Instead, always access the peers via `WeakReference<IJavaPeerable>.Target` which blocks if there is an ongoing bridge processing. * Do not modify the `RegisteredInstances` during _bridge processing_. Doing this would require taking a lock on `RegisteredInstances` which might already be locked in some other method of `ManagedValueManager` called from another thread (for example the `AddPeer` method) which might be blocked waiting on `WeakReference<IJavaPeerable>.Target` to return. For this reason, we have a queue of known dead weak references stored in the `RegisteredInstances` method which we fill at the end of bridge processing. This queue needs to be periodically emptied before calls to `AddPeer` and others to make sure that weak references stored in `RegisteredInstances` aren't leaking. * Do not trust the context pointers coming from the GC to be `HandleContext*`. Anyone can call the `JavaMarshal.CreateReferenceTrackingHandle(...)` method and "poison" the contexts that will be passed to us by the GC with pointers to memory we don't own and can't guarantee the size of the memory or even the fact that the memory won't be freed before the GC passes the pointer to our bridge processing callback. We keep a static dictionary of all the contexts and their associated GCHandles in `HandleContext` to validate the pointers we receive and also to map the contexts to their corresponding handles before calling `JavaMarshal.FinishBridgeProcessing`. #### `gc-bridge.hh+cc` This static class contains the main callback for the GC bridge (`GCBridge::mark_cross_references`). The GC expect this method to return immediately and do all the bridge processing in a separate thread. The input to this method is a pointer (`MarkCrossReferencesArgs *args`) which needs to be later passed to `JavaMarshal.FinishBridgeProcessing(...)` in order to be freed. This class also contains a background thread which waits for the next bridge processing event using a `std::binary_semaphore`. Once the thread is signaled there is a new bridge procesing event, it uses an `BridgeProcessing` object to process it. #### `bridge-processing.hh+cc` The `BridgeProcessing` class implements processing of a single bridge processing event. The code in this class is based on the Mono bridge processing algorithm implemented in [`src/native/mono/monodroid/osbridge.cc`][0]. [0]: https://github.com/dotnet/android/blob/8c0ca82e407761fac7ea959fa4d4819fa6e4eeac/src/native/mono/monodroid/osbridge.cc Co-authored-by: Jonathan Peppers <[email protected]> Co-authored-by: Marek Habersack <[email protected]>
1 parent 868acb2 commit 1f8f719

File tree

17 files changed

+1089
-111
lines changed

17 files changed

+1089
-111
lines changed

src/Mono.Android/Android.Runtime/AndroidRuntime.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,9 @@ class AndroidValueManager : JniRuntime.JniValueManager {
629629

630630
public override void WaitForGCBridgeProcessing ()
631631
{
632-
AndroidRuntimeInternal.WaitForBridgeProcessing ();
632+
if (!AndroidRuntimeInternal.BridgeProcessing)
633+
return;
634+
RuntimeNativeMethods._monodroid_gc_wait_for_bridge_processing ();
633635
}
634636

635637
public override IJavaPeerable? CreatePeer (

src/Mono.Android/Android.Runtime/AndroidRuntimeInternal.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,7 @@ static void MonoUnhandledException (Exception ex)
6464

6565
public static void WaitForBridgeProcessing ()
6666
{
67-
if (!BridgeProcessing)
68-
return;
69-
RuntimeNativeMethods._monodroid_gc_wait_for_bridge_processing ();
67+
Java.Interop.JniEnvironment.Runtime.ValueManager.WaitForGCBridgeProcessing ();
7068
}
7169
}
7270
}

src/Mono.Android/Android.Runtime/RuntimeNativeMethods.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System;
33
using System.Runtime.CompilerServices;
44
using System.Runtime.InteropServices;
5+
using System.Runtime.InteropServices.Java;
56
using System.Text;
67

78
namespace Android.Runtime
@@ -18,7 +19,7 @@ enum TraceKind : uint
1819
All = Java | Managed | Native | Signals,
1920
}
2021

21-
internal static class RuntimeNativeMethods
22+
internal unsafe static class RuntimeNativeMethods
2223
{
2324
[DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)]
2425
internal extern static void monodroid_log (LogLevel level, LogCategories category, string message);
@@ -92,6 +93,11 @@ internal static class RuntimeNativeMethods
9293
[DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)]
9394
internal static extern bool clr_typemap_java_to_managed (string java_type_name, out IntPtr managed_assembly_name, out uint managed_type_token_id);
9495

96+
[DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)]
97+
internal static extern delegate* unmanaged<MarkCrossReferencesArgs*, void> clr_initialize_gc_bridge (
98+
delegate* unmanaged<MarkCrossReferencesArgs*, void> bridge_processing_started_callback,
99+
delegate* unmanaged<MarkCrossReferencesArgs*, void> bridge_processing_finished_callback);
100+
95101
[MethodImplAttribute(MethodImplOptions.InternalCall)]
96102
internal static extern void monodroid_unhandled_exception (Exception javaException);
97103

0 commit comments

Comments
 (0)