From aca68c9cb040ba50a77da7165e3997f106642348 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 11 Feb 2026 12:55:58 +0100 Subject: [PATCH 1/6] Remove bridgeProcessingSemaphore, embed GCHandle in HandleContext, drop referenceTrackingHandles dict - Remove bridgeProcessingSemaphore: WaitForGCBridgeProcessing is now a no-op (any shared lock between bridge and managed threads causes 3-way deadlock) - Embed GCHandle directly in HandleContext struct as IntPtr field (eliminates need for Dictionary shared between threads) - Move EnsureAllContextsAreOurs behind #if DEBUG (uses HashSet instead of the removed Dictionary) - Net result: bridge thread and managed threads share NO synchronization primitives --- .../ManagedValueManager.cs | 70 ++++++++----------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs index e84a518971b..7aea3004cda 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs @@ -27,8 +27,6 @@ class ManagedValueManager : JniRuntime.JniValueManager bool disposed; - static readonly SemaphoreSlim bridgeProcessingSemaphore = new (1, 1); - static Lazy s_instance = new (() => new ManagedValueManager ()); public static ManagedValueManager GetOrCreateInstance () => s_instance.Value; @@ -53,8 +51,6 @@ void ThrowIfDisposed () public override void WaitForGCBridgeProcessing () { - bridgeProcessingSemaphore.Wait (); - bridgeProcessingSemaphore.Release (); } public unsafe override void CollectPeers () @@ -195,7 +191,7 @@ public override void RemovePeer (IJavaPeerable value) for (int i = peers.Count - 1; i >= 0; i--) { ReferenceTrackingHandle peer = peers [i]; - IJavaPeerable target = peer.Target; + IJavaPeerable? target = peer.Target; if (ReferenceEquals (value, target)) { peers.RemoveAt (i); peer.Dispose (); @@ -299,7 +295,7 @@ public override List GetSurfacedPeers () unsafe struct ReferenceTrackingHandle : IDisposable { - WeakReference _weakReference; + WeakReference _weakReference; HandleContext* _context; public bool BelongsToContext (HandleContext* context) @@ -308,7 +304,7 @@ public bool BelongsToContext (HandleContext* context) public ReferenceTrackingHandle (IJavaPeerable peer) { _context = HandleContext.Alloc (peer); - _weakReference = new WeakReference (peer); + _weakReference = new (peer); } public IJavaPeerable? Target @@ -321,7 +317,7 @@ public void Dispose () IJavaPeerable? target = Target; - GCHandle handle = HandleContext.GetAssociatedGCHandle (_context); + GCHandle handle = _context->AssociatedGCHandle; HandleContext.Free (ref _context); _weakReference.SetTarget (null); if (handle.IsAllocated) { @@ -337,10 +333,10 @@ public void Dispose () unsafe struct HandleContext { static readonly nuint Size = (nuint)Marshal.SizeOf (); - static readonly Dictionary referenceTrackingHandles = new (); int identityHashCode; IntPtr controlBlock; + IntPtr gcHandle; // GCHandle stored as IntPtr to keep blittable layout public int PeerIdentityHashCode => identityHashCode; public bool IsCollected @@ -363,40 +359,25 @@ private struct JniObjectReferenceControlBlock public int refs_added; } - public static GCHandle GetAssociatedGCHandle (HandleContext* context) - { - lock (referenceTrackingHandles) { - if (!referenceTrackingHandles.TryGetValue ((IntPtr) context, out GCHandle handle)) { - throw new InvalidOperationException ("Unknown reference tracking handle."); - } + public GCHandle AssociatedGCHandle => GCHandle.FromIntPtr (gcHandle); - return handle; - } - } +#if DEBUG + static readonly HashSet knownContexts = new (); public static unsafe void EnsureAllContextsAreOurs (MarkCrossReferencesArgs* mcr) { - lock (referenceTrackingHandles) { + lock (knownContexts) { for (nuint i = 0; i < mcr->ComponentCount; i++) { StronglyConnectedComponent component = mcr->Components [i]; - EnsureAllContextsInComponentAreOurs (component); - } - } - - static void EnsureAllContextsInComponentAreOurs (StronglyConnectedComponent component) - { - for (nuint i = 0; i < component.Count; i++) { - EnsureContextIsOurs ((IntPtr)component.Contexts [i]); + for (nuint j = 0; j < component.Count; j++) { + if (!knownContexts.Contains ((IntPtr)component.Contexts [j])) { + throw new InvalidOperationException ("Unknown reference tracking handle."); + } + } } } - - static void EnsureContextIsOurs (IntPtr context) - { - if (!referenceTrackingHandles.ContainsKey (context)) { - throw new InvalidOperationException ("Unknown reference tracking handle."); - } - } } +#endif public static HandleContext* Alloc (IJavaPeerable peer) { @@ -409,9 +390,13 @@ static void EnsureContextIsOurs (IntPtr context) context->controlBlock = peer.JniObjectReferenceControlBlock; GCHandle handle = JavaMarshal.CreateReferenceTrackingHandle (peer, context); - lock (referenceTrackingHandles) { - referenceTrackingHandles [(IntPtr) context] = handle; + context->gcHandle = GCHandle.ToIntPtr (handle); + +#if DEBUG + lock (knownContexts) { + knownContexts.Add ((IntPtr)context); } +#endif return context; } @@ -422,9 +407,11 @@ public static void Free (ref HandleContext* context) return; } - lock (referenceTrackingHandles) { - referenceTrackingHandles.Remove ((IntPtr)context); +#if DEBUG + lock (knownContexts) { + knownContexts.Remove ((IntPtr)context); } +#endif NativeMemory.Free (context); context = null; @@ -438,8 +425,9 @@ static unsafe void BridgeProcessingStarted (MarkCrossReferencesArgs* mcr) throw new ArgumentNullException (nameof (mcr), "MarkCrossReferencesArgs should never be null."); } +#if DEBUG HandleContext.EnsureAllContextsAreOurs (mcr); - bridgeProcessingSemaphore.Wait (); +#endif } [UnmanagedCallersOnly] @@ -451,8 +439,6 @@ static unsafe void BridgeProcessingFinished (MarkCrossReferencesArgs* mcr) ReadOnlySpan handlesToFree = ProcessCollectedContexts (mcr); JavaMarshal.FinishCrossReferenceProcessing (mcr, handlesToFree); - - bridgeProcessingSemaphore.Release (); } static unsafe ReadOnlySpan ProcessCollectedContexts (MarkCrossReferencesArgs* mcr) @@ -478,7 +464,7 @@ void ProcessContext (HandleContext* context) return; } - GCHandle handle = HandleContext.GetAssociatedGCHandle (context); + GCHandle handle = context->AssociatedGCHandle; // Note: modifying the RegisteredInstances dictionary while processing the collected contexts // is tricky and can lead to deadlocks, so we remember which contexts were collected and we will free From 60619077370f88283f3fcb370f97a63816c1f3b5 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 11 Feb 2026 13:50:27 +0100 Subject: [PATCH 2/6] Schedule CollectPeers on thread pool after bridge processing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove CollectPeers() call from AddPeer (no longer needed on hot path) - Schedule CollectPeers via Task.Run after FinishCrossReferenceProcessing (bridge thread must not take lock(RegisteredInstances) — deadlocks) - Bridge thread enqueues collected contexts, thread pool thread drains them --- .../Microsoft.Android.Runtime/ManagedValueManager.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs index 7aea3004cda..e2a35a44905 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs @@ -13,6 +13,7 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.Java; using System.Threading; +using System.Threading.Tasks; using Android.Runtime; using Java.Interop; @@ -91,9 +92,6 @@ public override void AddPeer (IJavaPeerable value) { ThrowIfDisposed (); - // Remove any collected contexts before adding a new peer. - CollectPeers (); - var r = value.PeerReference; if (!r.IsValid) throw new ObjectDisposedException (value.GetType ().FullName); @@ -439,6 +437,10 @@ static unsafe void BridgeProcessingFinished (MarkCrossReferencesArgs* mcr) ReadOnlySpan handlesToFree = ProcessCollectedContexts (mcr); JavaMarshal.FinishCrossReferenceProcessing (mcr, handlesToFree); + + // Schedule cleanup of RegisteredInstances on a thread pool thread. + // The bridge thread must not take lock(RegisteredInstances) — see deadlock notes. + Task.Run (GetOrCreateInstance ().CollectPeers); } static unsafe ReadOnlySpan ProcessCollectedContexts (MarkCrossReferencesArgs* mcr) From 6f8125eb34a9b21124cac9f5eb657a1f7ee9ea61 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 11 Feb 2026 14:32:02 +0100 Subject: [PATCH 3/6] Use dedicated System.Threading.Lock for RegisteredInstances Replace lock(RegisteredInstances) with a dedicated Lock instance. Benchmark: neutral (30,580 vs 30,220 iterations in 2min stress test). --- .../Microsoft.Android.Runtime/ManagedValueManager.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs index e2a35a44905..01905d6371c 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs @@ -23,6 +23,7 @@ class ManagedValueManager : JniRuntime.JniValueManager { const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; + readonly Lock _registeredInstancesLock = new (); readonly Dictionary> RegisteredInstances = new (); readonly ConcurrentQueue CollectedContexts = new (); @@ -62,7 +63,7 @@ public unsafe override void CollectPeers () Debug.Assert (contextPtr != IntPtr.Zero, "CollectedContexts should not contain null pointers."); HandleContext* context = (HandleContext*)contextPtr; - lock (RegisteredInstances) { + lock (_registeredInstancesLock) { Remove (context); } @@ -101,7 +102,7 @@ public override void AddPeer (IJavaPeerable value) JniObjectReference.Dispose (ref r, JniObjectReferenceOptions.CopyAndDispose); } int key = value.JniIdentityHashCode; - lock (RegisteredInstances) { + lock (_registeredInstancesLock) { List? peers; if (!RegisteredInstances.TryGetValue (key, out peers)) { peers = [new ReferenceTrackingHandle (value)]; @@ -154,7 +155,7 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal int key = GetJniIdentityHashCode (reference); - lock (RegisteredInstances) { + lock (_registeredInstancesLock) { if (!RegisteredInstances.TryGetValue (key, out List? peers)) return null; @@ -182,7 +183,7 @@ public override void RemovePeer (IJavaPeerable value) if (value == null) throw new ArgumentNullException (nameof (value)); - lock (RegisteredInstances) { + lock (_registeredInstancesLock) { int key = value.JniIdentityHashCode; if (!RegisteredInstances.TryGetValue (key, out List? peers)) return; @@ -278,7 +279,7 @@ public override List GetSurfacedPeers () // Remove any collected contexts before iterating over all the registered instances CollectPeers (); - lock (RegisteredInstances) { + lock (_registeredInstancesLock) { var peers = new List (RegisteredInstances.Count); foreach (var (identityHashCode, referenceTrackingHandles) in RegisteredInstances) { foreach (var peer in referenceTrackingHandles) { From 185d0b3e756806d41ab5ff489f7c2fae254c11da Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 11 Feb 2026 15:03:59 +0100 Subject: [PATCH 4/6] Apply field naming conventions: _prefix for instance, s_prefix for static --- .../ManagedValueManager.cs | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs index 01905d6371c..a7c32941e51 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs @@ -24,10 +24,10 @@ class ManagedValueManager : JniRuntime.JniValueManager const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; readonly Lock _registeredInstancesLock = new (); - readonly Dictionary> RegisteredInstances = new (); - readonly ConcurrentQueue CollectedContexts = new (); + readonly Dictionary> _registeredInstances = new (); + readonly ConcurrentQueue _collectedContexts = new (); - bool disposed; + bool _disposed; static Lazy s_instance = new (() => new ManagedValueManager ()); public static ManagedValueManager GetOrCreateInstance () => s_instance.Value; @@ -41,13 +41,13 @@ unsafe ManagedValueManager () protected override void Dispose (bool disposing) { - disposed = true; + _disposed = true; base.Dispose (disposing); } void ThrowIfDisposed () { - if (disposed) + if (_disposed) throw new ObjectDisposedException (nameof (ManagedValueManager)); } @@ -59,8 +59,8 @@ public unsafe override void CollectPeers () { ThrowIfDisposed (); - while (CollectedContexts.TryDequeue (out IntPtr contextPtr)) { - Debug.Assert (contextPtr != IntPtr.Zero, "CollectedContexts should not contain null pointers."); + while (_collectedContexts.TryDequeue (out IntPtr contextPtr)) { + Debug.Assert (contextPtr != IntPtr.Zero, "_collectedContexts should not contain null pointers."); HandleContext* context = (HandleContext*)contextPtr; lock (_registeredInstancesLock) { @@ -73,7 +73,7 @@ public unsafe override void CollectPeers () void Remove (HandleContext* context) { int key = context->PeerIdentityHashCode; - if (!RegisteredInstances.TryGetValue (key, out List? peers)) + if (!_registeredInstances.TryGetValue (key, out List? peers)) return; for (int i = peers.Count - 1; i >= 0; i--) { @@ -84,7 +84,7 @@ void Remove (HandleContext* context) } if (peers.Count == 0) { - RegisteredInstances.Remove (key); + _registeredInstances.Remove (key); } } } @@ -104,9 +104,9 @@ public override void AddPeer (IJavaPeerable value) int key = value.JniIdentityHashCode; lock (_registeredInstancesLock) { List? peers; - if (!RegisteredInstances.TryGetValue (key, out peers)) { + if (!_registeredInstances.TryGetValue (key, out peers)) { peers = [new ReferenceTrackingHandle (value)]; - RegisteredInstances.Add (key, peers); + _registeredInstances.Add (key, peers); return; } @@ -156,7 +156,7 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal int key = GetJniIdentityHashCode (reference); lock (_registeredInstancesLock) { - if (!RegisteredInstances.TryGetValue (key, out List? peers)) + if (!_registeredInstances.TryGetValue (key, out List? peers)) return null; for (int i = peers.Count - 1; i >= 0; i--) { @@ -168,7 +168,7 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal } if (peers.Count == 0) - RegisteredInstances.Remove (key); + _registeredInstances.Remove (key); } return null; } @@ -177,7 +177,7 @@ public override void RemovePeer (IJavaPeerable value) { ThrowIfDisposed (); - // Remove any collected contexts before modifying RegisteredInstances + // Remove any collected contexts before modifying _registeredInstances CollectPeers (); if (value == null) @@ -185,7 +185,7 @@ public override void RemovePeer (IJavaPeerable value) lock (_registeredInstancesLock) { int key = value.JniIdentityHashCode; - if (!RegisteredInstances.TryGetValue (key, out List? peers)) + if (!_registeredInstances.TryGetValue (key, out List? peers)) return; for (int i = peers.Count - 1; i >= 0; i--) { @@ -198,7 +198,7 @@ public override void RemovePeer (IJavaPeerable value) GC.KeepAlive (target); } if (peers.Count == 0) - RegisteredInstances.Remove (key); + _registeredInstances.Remove (key); } } @@ -280,8 +280,8 @@ public override List GetSurfacedPeers () CollectPeers (); lock (_registeredInstancesLock) { - var peers = new List (RegisteredInstances.Count); - foreach (var (identityHashCode, referenceTrackingHandles) in RegisteredInstances) { + var peers = new List (_registeredInstances.Count); + foreach (var (identityHashCode, referenceTrackingHandles) in _registeredInstances) { foreach (var peer in referenceTrackingHandles) { if (peer.Target is IJavaPeerable target) { peers.Add (new JniSurfacedPeerInfo (identityHashCode, new WeakReference (target))); @@ -333,19 +333,19 @@ unsafe struct HandleContext { static readonly nuint Size = (nuint)Marshal.SizeOf (); - int identityHashCode; - IntPtr controlBlock; - IntPtr gcHandle; // GCHandle stored as IntPtr to keep blittable layout + int _identityHashCode; + IntPtr _controlBlock; + IntPtr _gcHandle; // GCHandle stored as IntPtr to keep blittable layout - public int PeerIdentityHashCode => identityHashCode; + public int PeerIdentityHashCode => _identityHashCode; public bool IsCollected { get { - if (controlBlock == IntPtr.Zero) + if (_controlBlock == IntPtr.Zero) throw new InvalidOperationException ("HandleContext control block is not initialized."); - return ((JniObjectReferenceControlBlock*) controlBlock)->handle == IntPtr.Zero; + return ((JniObjectReferenceControlBlock*) _controlBlock)->handle == IntPtr.Zero; } } @@ -358,18 +358,18 @@ private struct JniObjectReferenceControlBlock public int refs_added; } - public GCHandle AssociatedGCHandle => GCHandle.FromIntPtr (gcHandle); + public GCHandle AssociatedGCHandle => GCHandle.FromIntPtr (_gcHandle); #if DEBUG - static readonly HashSet knownContexts = new (); + static readonly HashSet s_knownContexts = new (); public static unsafe void EnsureAllContextsAreOurs (MarkCrossReferencesArgs* mcr) { - lock (knownContexts) { + lock (s_knownContexts) { for (nuint i = 0; i < mcr->ComponentCount; i++) { StronglyConnectedComponent component = mcr->Components [i]; for (nuint j = 0; j < component.Count; j++) { - if (!knownContexts.Contains ((IntPtr)component.Contexts [j])) { + if (!s_knownContexts.Contains ((IntPtr)component.Contexts [j])) { throw new InvalidOperationException ("Unknown reference tracking handle."); } } @@ -385,15 +385,15 @@ public static unsafe void EnsureAllContextsAreOurs (MarkCrossReferencesArgs* mcr throw new OutOfMemoryException ("Failed to allocate memory for HandleContext."); } - context->identityHashCode = peer.JniIdentityHashCode; - context->controlBlock = peer.JniObjectReferenceControlBlock; + context->_identityHashCode = peer.JniIdentityHashCode; + context->_controlBlock = peer.JniObjectReferenceControlBlock; GCHandle handle = JavaMarshal.CreateReferenceTrackingHandle (peer, context); - context->gcHandle = GCHandle.ToIntPtr (handle); + context->_gcHandle = GCHandle.ToIntPtr (handle); #if DEBUG - lock (knownContexts) { - knownContexts.Add ((IntPtr)context); + lock (s_knownContexts) { + s_knownContexts.Add ((IntPtr)context); } #endif @@ -407,8 +407,8 @@ public static void Free (ref HandleContext* context) } #if DEBUG - lock (knownContexts) { - knownContexts.Remove ((IntPtr)context); + lock (s_knownContexts) { + s_knownContexts.Remove ((IntPtr)context); } #endif @@ -439,8 +439,8 @@ static unsafe void BridgeProcessingFinished (MarkCrossReferencesArgs* mcr) ReadOnlySpan handlesToFree = ProcessCollectedContexts (mcr); JavaMarshal.FinishCrossReferenceProcessing (mcr, handlesToFree); - // Schedule cleanup of RegisteredInstances on a thread pool thread. - // The bridge thread must not take lock(RegisteredInstances) — see deadlock notes. + // Schedule cleanup of _registeredInstances on a thread pool thread. + // The bridge thread must not take lock(_registeredInstances) — see deadlock notes. Task.Run (GetOrCreateInstance ().CollectPeers); } @@ -469,10 +469,10 @@ void ProcessContext (HandleContext* context) GCHandle handle = context->AssociatedGCHandle; - // Note: modifying the RegisteredInstances dictionary while processing the collected contexts + // Note: modifying the _registeredInstances dictionary while processing the collected contexts // is tricky and can lead to deadlocks, so we remember which contexts were collected and we will free // them later outside of the bridge processing loop. - instance.CollectedContexts.Enqueue ((IntPtr)context); + instance._collectedContexts.Enqueue ((IntPtr)context); // important: we must not free the handle before passing it to JavaMarshal.FinishCrossReferenceProcessing handlesToFree.Add (handle); From 2e3f56daa9921d20fe0e456c88e2ee60c3bd96f1 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 11:04:11 +0100 Subject: [PATCH 5/6] Refactor singleton access and remove synchronous cleanup from WaitForGCBridgeProcessing (#10806) * Initial plan * Make WaitForGCBridgeProcessing synchronously drain pending contexts Fixes test failure in Dispose_Finalized by ensuring WaitForGCBridgeProcessing calls CollectPeers() to drain any contexts enqueued during bridge processing. Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> * Use TryGetInstance in bridge processing to avoid creating instance Add TryGetInstance() helper that checks IsValueCreated before accessing Value. Use it in BridgeProcessingFinished and ProcessCollectedContexts to avoid creating ManagedValueManager instance unnecessarily during bridge processing. Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> * Rename TryGetInstance to GetInstanceIfCreated and clarify comments Follow conventional naming: GetInstanceIfCreated (returns null) vs Try* (returns bool with out param). Clarify comments to reflect defensive checking rather than invariants. Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> * Rename GetOrCreateInstance to Instance property and make WaitForGCBridgeProcessing a no-op - Replace GetOrCreateInstance() method with Instance property getter for cleaner singleton pattern - Update JNIEnvInit.cs to use the new Instance property - Make WaitForGCBridgeProcessing() a no-op (remove CollectPeers call) - Update JavaObjectTest.WaitForGC() to explicitly call CollectPeers() after WaitForGCBridgeProcessing() Note: JavaObjectTest.cs change is in external/Java.Interop submodule and will need to be committed separately in the Java.Interop repository. Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> * Remove GetInstanceIfCreated and use Instance everywhere Remove the defensive null checks in bridge processing. If bridge processing is being called, the instance must already exist since objects need to be registered for bridge processing to happen. Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> --- src/Mono.Android/Android.Runtime/JNIEnvInit.cs | 2 +- .../Microsoft.Android.Runtime/ManagedValueManager.cs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index 71b370a3cee..65741304b35 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -139,7 +139,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) if (RuntimeFeature.IsMonoRuntime) { valueManager = new AndroidValueManager (); } else if (RuntimeFeature.IsCoreClrRuntime) { - valueManager = ManagedValueManager.GetOrCreateInstance (); + valueManager = ManagedValueManager.Instance; } else { throw new NotSupportedException ("Internal error: unknown runtime not supported"); } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs index a7c32941e51..27ceb09d019 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs @@ -30,7 +30,8 @@ class ManagedValueManager : JniRuntime.JniValueManager bool _disposed; static Lazy s_instance = new (() => new ManagedValueManager ()); - public static ManagedValueManager GetOrCreateInstance () => s_instance.Value; + + public static ManagedValueManager Instance => s_instance.Value; unsafe ManagedValueManager () { @@ -441,13 +442,13 @@ static unsafe void BridgeProcessingFinished (MarkCrossReferencesArgs* mcr) // Schedule cleanup of _registeredInstances on a thread pool thread. // The bridge thread must not take lock(_registeredInstances) — see deadlock notes. - Task.Run (GetOrCreateInstance ().CollectPeers); + Task.Run (Instance.CollectPeers); } static unsafe ReadOnlySpan ProcessCollectedContexts (MarkCrossReferencesArgs* mcr) { List handlesToFree = []; - ManagedValueManager instance = GetOrCreateInstance (); + ManagedValueManager instance = Instance; for (int i = 0; (nuint)i < mcr->ComponentCount; i++) { StronglyConnectedComponent component = mcr->Components [i]; From c87e31838f2da70b272544d48e376cb549335ffc Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 19 Feb 2026 10:09:15 +0100 Subject: [PATCH 6/6] Fix remaining GetOrCreateInstance() references to Instance Update JreRuntime.cs and JavaInteropRuntime.cs to use the renamed ManagedValueManager.Instance property instead of the removed GetOrCreateInstance() method. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Android.Runtime.NativeAOT/JavaInteropRuntime.cs | 2 +- .../Java.Interop/JreRuntime.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs index 45a82899bba..dbdc1eb4327 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs @@ -62,7 +62,7 @@ static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader, IntPtr langua EnvironmentPointer = jnienv, ClassLoader = new JniObjectReference (classLoader, JniObjectReferenceType.Global), TypeManager = new ManagedTypeManager (), - ValueManager = ManagedValueManager.GetOrCreateInstance (), + ValueManager = ManagedValueManager.Instance, UseMarshalMemberBuilder = false, JniGlobalReferenceLogWriter = settings.GrefLog, JniLocalReferenceLogWriter = settings.LrefLog, diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs index 4e70da25a0e..afe50d3ea24 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs @@ -61,7 +61,7 @@ static NativeAotRuntimeOptions CreateJreVM (NativeAotRuntimeOptions builder) builder.TypeManager ??= new ManagedTypeManager (); #endif // NET - builder.ValueManager ??= ManagedValueManager.GetOrCreateInstance(); + builder.ValueManager ??= ManagedValueManager.Instance; builder.ObjectReferenceManager ??= new Android.Runtime.AndroidObjectReferenceManager (); if (builder.InvocationPointer != IntPtr.Zero || builder.EnvironmentPointer != IntPtr.Zero)