Skip to content

Commit 584b7ce

Browse files
Address code review feedback on GC bridge implementation
- Remove explicit Cdecl calling convention from UnmanagedCallersOnly and function pointers (Cdecl is default on Linux/Android) - Restore bridgeProcessingSemaphore for WaitForGCBridgeProcessing - Convert BridgeProcessing.Initialize to static constructor using RuntimeFeature - Replace CrossReferenceTarget struct with ValueTuple - Rename peer variable to peerToDispose for clarity - Simplify AddReference/ClearReferences by removing outer try-catch blocks Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
1 parent e6ccf05 commit 584b7ce

3 files changed

Lines changed: 77 additions & 102 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ internal unsafe static class RuntimeNativeMethods
9292
/// </summary>
9393
[DllImport (RuntimeConstants.InternalDllName, CallingConvention = CallingConvention.Cdecl)]
9494
internal static extern delegate* unmanaged<void*, void> clr_gc_bridge_initialize_for_managed_processing (
95-
delegate* unmanaged[Cdecl]<void*, void> onMarkCrossReferencesCallback);
95+
delegate* unmanaged<void*, void> onMarkCrossReferencesCallback);
9696

9797
[MethodImplAttribute(MethodImplOptions.InternalCall)]
9898
internal static extern void monodroid_unhandled_exception (Exception javaException);

src/Mono.Android/Microsoft.Android.Runtime/BridgeProcessing.cs

Lines changed: 55 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -34,41 +34,17 @@ unsafe class BridgeProcessing
3434
static JniMethodInfo? s_Runtime_gc;
3535

3636
// Is NativeAOT mode (uses GCUserPeerable instead of IGCUserPeer)
37-
static bool s_initialized;
38-
static bool s_isNativeAOT;
37+
static readonly bool s_isNativeAOT;
3938

40-
public BridgeProcessing (MarkCrossReferencesArgs* args)
41-
{
42-
if (args == null) {
43-
throw new ArgumentNullException (nameof (args), "Cross references argument is a NULL pointer");
44-
}
45-
46-
if (args->ComponentCount > 0 && args->Components == null) {
47-
throw new InvalidOperationException ("Components member of the cross references arguments structure is NULL");
48-
}
49-
50-
if (args->CrossReferenceCount > 0 && args->CrossReferences == null) {
51-
throw new InvalidOperationException ("CrossReferences member of the cross references arguments structure is NULL");
52-
}
53-
54-
crossRefs = args;
55-
}
56-
57-
/// <summary>
58-
/// Initialize cached Java references. Must be called once during runtime initialization.
59-
/// </summary>
60-
public static void Initialize (bool isNativeAOT)
39+
static BridgeProcessing ()
6140
{
62-
if (s_initialized)
63-
return;
64-
65-
s_isNativeAOT = isNativeAOT;
41+
s_isNativeAOT = !RuntimeFeature.IsMonoRuntime;
6642

6743
// Initialize GCUserPeer class for creating temporary peers
6844
s_GCUserPeerClass = new JniType ("mono/android/GCUserPeer");
6945
s_GCUserPeerCtor = s_GCUserPeerClass.GetConstructor ("()V");
7046

71-
if (isNativeAOT) {
47+
if (s_isNativeAOT) {
7248
// For NativeAOT, we also use GCUserPeerable interface for optimized method calls
7349
s_GCUserPeerableClass = new JniType ("net/dot/jni/GCUserPeerable");
7450
s_GCUserPeerable_jiAddManagedReference = s_GCUserPeerableClass.GetInstanceMethod ("jiAddManagedReference", "(Ljava/lang/Object;)V");
@@ -82,8 +58,23 @@ public static void Initialize (bool isNativeAOT)
8258
var runtimeLocal = JniEnvironment.StaticMethods.CallStaticObjectMethod (runtimeClass.PeerReference, getRuntimeMethod, null);
8359
s_RuntimeInstance = runtimeLocal.NewGlobalRef ();
8460
JniObjectReference.Dispose (ref runtimeLocal);
61+
}
8562

86-
s_initialized = true;
63+
public BridgeProcessing (MarkCrossReferencesArgs* args)
64+
{
65+
if (args == null) {
66+
throw new ArgumentNullException (nameof (args), "Cross references argument is a NULL pointer");
67+
}
68+
69+
if (args->ComponentCount > 0 && args->Components == null) {
70+
throw new InvalidOperationException ("Components member of the cross references arguments structure is NULL");
71+
}
72+
73+
if (args->CrossReferenceCount > 0 && args->CrossReferences == null) {
74+
throw new InvalidOperationException ("CrossReferences member of the cross references arguments structure is NULL");
75+
}
76+
77+
crossRefs = args;
8778
}
8879

8980
/// <summary>
@@ -117,8 +108,8 @@ void PrepareForJavaCollection ()
117108

118109
// With cross references processed, the temporary peer list can be released
119110
foreach (var (_, temporaryPeer) in temporaryPeers) {
120-
var peer = temporaryPeer;
121-
JniObjectReference.Dispose (ref peer);
111+
var peerToDispose = temporaryPeer;
112+
JniObjectReference.Dispose (ref peerToDispose);
122113
}
123114

124115
// Switch global to weak references
@@ -177,52 +168,35 @@ void AddCircularReferences (ref StronglyConnectedComponent scc)
177168

178169
void AddCrossReference (nuint sourceIndex, nuint destIndex)
179170
{
180-
CrossReferenceTarget from = SelectCrossReferenceTarget (sourceIndex);
181-
CrossReferenceTarget to = SelectCrossReferenceTarget (destIndex);
171+
var (fromRef, fromContextPtr) = SelectCrossReferenceTarget (sourceIndex);
172+
var (toRef, _) = SelectCrossReferenceTarget (destIndex);
182173

183-
if (AddReference (from.Reference, to.Reference)) {
184-
from.MarkRefsAddedIfNeeded ();
174+
if (AddReference (fromRef, toRef) && fromContextPtr != IntPtr.Zero) {
175+
HandleContext* fromContext = (HandleContext*)fromContextPtr;
176+
fromContext->ControlBlock->RefsAdded = 1;
185177
}
186178
}
187179

188-
readonly struct CrossReferenceTarget
189-
{
190-
public readonly JniObjectReference Reference;
191-
public readonly HandleContext* Context;
192-
public readonly bool IsTemporaryPeer;
193-
194-
public CrossReferenceTarget (JniObjectReference reference, HandleContext* context, bool isTemporaryPeer)
195-
{
196-
Reference = reference;
197-
Context = context;
198-
IsTemporaryPeer = isTemporaryPeer;
199-
}
200-
201-
public void MarkRefsAddedIfNeeded ()
202-
{
203-
if (IsTemporaryPeer || Context == null) {
204-
return;
205-
}
206-
Context->ControlBlock->RefsAdded = 1;
207-
}
208-
}
209-
210-
CrossReferenceTarget SelectCrossReferenceTarget (nuint sccIndex)
180+
/// <summary>
181+
/// Selects the target for a cross-reference from an SCC.
182+
/// Returns the JNI reference and the handle context pointer (IntPtr.Zero for temporary peers).
183+
/// </summary>
184+
(JniObjectReference Reference, IntPtr ContextPtr) SelectCrossReferenceTarget (nuint sccIndex)
211185
{
212186
ref StronglyConnectedComponent scc = ref crossRefs->Components[sccIndex];
213187

214188
if (scc.Count == 0) {
215189
if (!temporaryPeers.TryGetValue (sccIndex, out var tempPeer)) {
216190
throw new InvalidOperationException ("Temporary peer must be found in the map");
217191
}
218-
return new CrossReferenceTarget (tempPeer, null, isTemporaryPeer: true);
192+
return (tempPeer, IntPtr.Zero);
219193
}
220194

221195
HandleContext* context = (HandleContext*)scc.Contexts[0];
222196
Debug.Assert (context != null && context->ControlBlock != null, "SCC must have at least one valid context");
223197

224198
var reference = new JniObjectReference (context->ControlBlock->Handle, JniObjectReferenceType.Global);
225-
return new CrossReferenceTarget (reference, context, isTemporaryPeer: false);
199+
return (reference, (IntPtr)context);
226200
}
227201

228202
bool AddReference (JniObjectReference from, JniObjectReference to)
@@ -238,23 +212,20 @@ bool AddReference (JniObjectReference from, JniObjectReference to)
238212

239213
// Fall back to reflection-based approach
240214
var fromClassRef = JniEnvironment.Types.GetObjectClass (from);
241-
try {
242-
using var fromClass = new JniType (ref fromClassRef, JniObjectReferenceOptions.CopyAndDispose);
243-
JniMethodInfo addMethod;
244-
try {
245-
addMethod = fromClass.GetInstanceMethod ("monodroidAddReference", "(Ljava/lang/Object;)V");
246-
} catch (Java.Lang.NoSuchMethodError) {
247-
Logger.Log (LogLevel.Error, "monodroid-gc", "Failed to find monodroidAddReference method");
248-
return false;
249-
}
215+
using var fromClass = new JniType (ref fromClassRef, JniObjectReferenceOptions.CopyAndDispose);
250216

251-
JniArgumentValue* args = stackalloc JniArgumentValue[1];
252-
args[0] = new JniArgumentValue (to);
253-
JniEnvironment.InstanceMethods.CallVoidMethod (from, addMethod, args);
254-
return true;
255-
} catch {
217+
JniMethodInfo addMethod;
218+
try {
219+
addMethod = fromClass.GetInstanceMethod ("monodroidAddReference", "(Ljava/lang/Object;)V");
220+
} catch (Java.Lang.NoSuchMethodError) {
221+
Logger.Log (LogLevel.Error, "monodroid-gc", "Failed to find monodroidAddReference method");
256222
return false;
257223
}
224+
225+
JniArgumentValue* args = stackalloc JniArgumentValue[1];
226+
args[0] = new JniArgumentValue (to);
227+
JniEnvironment.InstanceMethods.CallVoidMethod (from, addMethod, args);
228+
return true;
258229
}
259230

260231
bool TryCallGCUserPeerableAddManagedReference (JniObjectReference from, JniObjectReference to)
@@ -279,7 +250,7 @@ bool TryCallGCUserPeerableAddManagedReference (JniObjectReference from, JniObjec
279250
static void TriggerJavaGC ()
280251
{
281252
if (s_Runtime_gc == null) {
282-
throw new InvalidOperationException ("BridgeProcessing.Initialize must be called before TriggerJavaGC");
253+
throw new InvalidOperationException ("BridgeProcessing static constructor must run before TriggerJavaGC");
283254
}
284255

285256
try {
@@ -370,20 +341,17 @@ void ClearReferences (JniObjectReference handle)
370341

371342
// Fall back to reflection-based approach
372343
var javaClassRef = JniEnvironment.Types.GetObjectClass (handle);
373-
try {
374-
using var javaClass = new JniType (ref javaClassRef, JniObjectReferenceOptions.CopyAndDispose);
375-
JniMethodInfo clearMethod;
376-
try {
377-
clearMethod = javaClass.GetInstanceMethod ("monodroidClearReferences", "()V");
378-
} catch (Java.Lang.NoSuchMethodError) {
379-
Logger.Log (LogLevel.Error, "monodroid-gc", "Failed to find monodroidClearReferences method");
380-
return;
381-
}
344+
using var javaClass = new JniType (ref javaClassRef, JniObjectReferenceOptions.CopyAndDispose);
382345

383-
JniEnvironment.InstanceMethods.CallVoidMethod (handle, clearMethod, null);
384-
} catch {
385-
// Ignore exceptions during cleanup
346+
JniMethodInfo clearMethod;
347+
try {
348+
clearMethod = javaClass.GetInstanceMethod ("monodroidClearReferences", "()V");
349+
} catch (Java.Lang.NoSuchMethodError) {
350+
Logger.Log (LogLevel.Error, "monodroid-gc", "Failed to find monodroidClearReferences method");
351+
return;
386352
}
353+
354+
JniEnvironment.InstanceMethods.CallVoidMethod (handle, clearMethod, null);
387355
}
388356

389357
bool TryCallGCUserPeerableClearManagedReferences (JniObjectReference handle)

src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,34 +28,41 @@ class ManagedValueManager : JniRuntime.JniValueManager
2828

2929
bool disposed;
3030

31+
static readonly SemaphoreSlim bridgeProcessingSemaphore = new (1, 1);
32+
3133
static Lazy<ManagedValueManager> s_instance = new (() => new ManagedValueManager ());
3234
public static ManagedValueManager GetOrCreateInstance () => s_instance.Value;
3335

3436
/// <summary>
3537
/// Callback invoked by native background thread to process bridge args.
3638
/// This runs on the native bridge processing thread, not a managed thread pool thread.
3739
/// </summary>
38-
[UnmanagedCallersOnly (CallConvs = [typeof (CallConvCdecl)])]
40+
[UnmanagedCallersOnly]
3941
static unsafe void OnBridgeProcessing (void* argsPtr)
4042
{
4143
MarkCrossReferencesArgs* args = (MarkCrossReferencesArgs*)argsPtr;
4244

43-
// Validate contexts
44-
HandleContext.EnsureAllContextsAreOurs (args);
45-
46-
// Do the actual bridge processing in managed code
47-
var processing = new BridgeProcessing (args);
48-
processing.Process ();
49-
50-
// Process collected contexts and finish
51-
ReadOnlySpan<GCHandle> handlesToFree = ProcessCollectedContexts (args);
52-
JavaMarshal.FinishCrossReferenceProcessing (args, handlesToFree);
45+
bridgeProcessingSemaphore.Wait ();
46+
try {
47+
// Validate contexts
48+
HandleContext.EnsureAllContextsAreOurs (args);
49+
50+
// Do the actual bridge processing in managed code
51+
var processing = new BridgeProcessing (args);
52+
processing.Process ();
53+
54+
// Process collected contexts and finish
55+
ReadOnlySpan<GCHandle> handlesToFree = ProcessCollectedContexts (args);
56+
JavaMarshal.FinishCrossReferenceProcessing (args, handlesToFree);
57+
} finally {
58+
bridgeProcessingSemaphore.Release ();
59+
}
5360
}
5461

5562
unsafe ManagedValueManager ()
5663
{
5764
// Initialize GC bridge with our callback that will be invoked from the native background thread
58-
delegate* unmanaged[Cdecl]<void*, void> callback = &OnBridgeProcessing;
65+
delegate* unmanaged<void*, void> callback = &OnBridgeProcessing;
5966
var mark_cross_references_ftn = RuntimeNativeMethods.clr_gc_bridge_initialize_for_managed_processing (callback);
6067
JavaMarshal.Initialize (mark_cross_references_ftn);
6168
}
@@ -74,8 +81,8 @@ void ThrowIfDisposed ()
7481

7582
public override void WaitForGCBridgeProcessing ()
7683
{
77-
// No-op: The native code guarantees mark_cross_references won't be called again
78-
// until FinishCrossReferenceProcessing returns, so there's no need to wait.
84+
bridgeProcessingSemaphore.Wait ();
85+
bridgeProcessingSemaphore.Release ();
7986
}
8087

8188
public unsafe override void CollectPeers ()

0 commit comments

Comments
 (0)