-
Notifications
You must be signed in to change notification settings - Fork 568
[CoreCLR/NativeAOT] Simplify synchronization in ManagedValueManager #10796
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
aca68c9
6061907
6f8125e
185d0b3
2e3f56d
c87e318
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
||
|
|
@@ -22,12 +23,11 @@ class ManagedValueManager : JniRuntime.JniValueManager | |
| { | ||
| const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; | ||
|
|
||
| readonly Dictionary<int, List<ReferenceTrackingHandle>> RegisteredInstances = new (); | ||
| readonly ConcurrentQueue<IntPtr> CollectedContexts = new (); | ||
| readonly Lock _registeredInstancesLock = new (); | ||
| readonly Dictionary<int, List<ReferenceTrackingHandle>> _registeredInstances = new (); | ||
| readonly ConcurrentQueue<IntPtr> _collectedContexts = new (); | ||
|
|
||
| bool disposed; | ||
|
|
||
| static readonly SemaphoreSlim bridgeProcessingSemaphore = new (1, 1); | ||
| bool _disposed; | ||
|
|
||
| static Lazy<ManagedValueManager> s_instance = new (() => new ManagedValueManager ()); | ||
| public static ManagedValueManager GetOrCreateInstance () => s_instance.Value; | ||
|
|
@@ -41,31 +41,29 @@ 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)); | ||
| } | ||
|
|
||
| public override void WaitForGCBridgeProcessing () | ||
| { | ||
| bridgeProcessingSemaphore.Wait (); | ||
| bridgeProcessingSemaphore.Release (); | ||
| } | ||
|
|
||
| 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 (RegisteredInstances) { | ||
| lock (_registeredInstancesLock) { | ||
| Remove (context); | ||
| } | ||
|
|
||
|
|
@@ -75,7 +73,7 @@ public unsafe override void CollectPeers () | |
| void Remove (HandleContext* context) | ||
| { | ||
| int key = context->PeerIdentityHashCode; | ||
| if (!RegisteredInstances.TryGetValue (key, out List<ReferenceTrackingHandle>? peers)) | ||
| if (!_registeredInstances.TryGetValue (key, out List<ReferenceTrackingHandle>? peers)) | ||
| return; | ||
|
|
||
| for (int i = peers.Count - 1; i >= 0; i--) { | ||
|
|
@@ -86,7 +84,7 @@ void Remove (HandleContext* context) | |
| } | ||
|
|
||
| if (peers.Count == 0) { | ||
| RegisteredInstances.Remove (key); | ||
| _registeredInstances.Remove (key); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -95,9 +93,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); | ||
|
|
@@ -107,11 +102,11 @@ public override void AddPeer (IJavaPeerable value) | |
| JniObjectReference.Dispose (ref r, JniObjectReferenceOptions.CopyAndDispose); | ||
| } | ||
| int key = value.JniIdentityHashCode; | ||
| lock (RegisteredInstances) { | ||
| lock (_registeredInstancesLock) { | ||
| List<ReferenceTrackingHandle>? 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; | ||
| } | ||
|
|
||
|
|
@@ -160,8 +155,8 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal | |
|
|
||
| int key = GetJniIdentityHashCode (reference); | ||
|
|
||
| lock (RegisteredInstances) { | ||
| if (!RegisteredInstances.TryGetValue (key, out List<ReferenceTrackingHandle>? peers)) | ||
| lock (_registeredInstancesLock) { | ||
| if (!_registeredInstances.TryGetValue (key, out List<ReferenceTrackingHandle>? peers)) | ||
| return null; | ||
|
|
||
| for (int i = peers.Count - 1; i >= 0; i--) { | ||
|
|
@@ -173,7 +168,7 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal | |
| } | ||
|
|
||
| if (peers.Count == 0) | ||
| RegisteredInstances.Remove (key); | ||
| _registeredInstances.Remove (key); | ||
| } | ||
| return null; | ||
| } | ||
|
|
@@ -182,28 +177,28 @@ 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) | ||
| throw new ArgumentNullException (nameof (value)); | ||
|
|
||
| lock (RegisteredInstances) { | ||
| lock (_registeredInstancesLock) { | ||
| int key = value.JniIdentityHashCode; | ||
| if (!RegisteredInstances.TryGetValue (key, out List<ReferenceTrackingHandle>? peers)) | ||
| if (!_registeredInstances.TryGetValue (key, out List<ReferenceTrackingHandle>? peers)) | ||
| return; | ||
|
|
||
| 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 (); | ||
| } | ||
| GC.KeepAlive (target); | ||
| } | ||
| if (peers.Count == 0) | ||
| RegisteredInstances.Remove (key); | ||
| _registeredInstances.Remove (key); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -284,9 +279,9 @@ public override List<JniSurfacedPeerInfo> GetSurfacedPeers () | |
| // Remove any collected contexts before iterating over all the registered instances | ||
| CollectPeers (); | ||
|
|
||
| lock (RegisteredInstances) { | ||
| var peers = new List<JniSurfacedPeerInfo> (RegisteredInstances.Count); | ||
| foreach (var (identityHashCode, referenceTrackingHandles) in RegisteredInstances) { | ||
| lock (_registeredInstancesLock) { | ||
| var peers = new List<JniSurfacedPeerInfo> (_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<IJavaPeerable> (target))); | ||
|
|
@@ -299,7 +294,7 @@ public override List<JniSurfacedPeerInfo> GetSurfacedPeers () | |
|
|
||
| unsafe struct ReferenceTrackingHandle : IDisposable | ||
| { | ||
| WeakReference<IJavaPeerable> _weakReference; | ||
| WeakReference<IJavaPeerable?> _weakReference; | ||
| HandleContext* _context; | ||
|
|
||
| public bool BelongsToContext (HandleContext* context) | ||
|
|
@@ -308,7 +303,7 @@ public bool BelongsToContext (HandleContext* context) | |
| public ReferenceTrackingHandle (IJavaPeerable peer) | ||
| { | ||
| _context = HandleContext.Alloc (peer); | ||
| _weakReference = new WeakReference<IJavaPeerable> (peer); | ||
| _weakReference = new (peer); | ||
| } | ||
|
|
||
| public IJavaPeerable? Target | ||
|
|
@@ -321,7 +316,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,20 +332,20 @@ public void Dispose () | |
| unsafe struct HandleContext | ||
| { | ||
| static readonly nuint Size = (nuint)Marshal.SizeOf<HandleContext> (); | ||
| static readonly Dictionary<IntPtr, GCHandle> referenceTrackingHandles = new (); | ||
|
|
||
| int identityHashCode; | ||
| IntPtr controlBlock; | ||
| 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; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -363,40 +358,25 @@ private struct JniObjectReferenceControlBlock | |
| public int refs_added; | ||
| } | ||
|
|
||
| public static GCHandle GetAssociatedGCHandle (HandleContext* context) | ||
| { | ||
| lock (referenceTrackingHandles) { | ||
| if (!referenceTrackingHandles.TryGetValue ((IntPtr) context, out GCHandle handle)) { | ||
simonrozsival marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| throw new InvalidOperationException ("Unknown reference tracking handle."); | ||
| } | ||
| public GCHandle AssociatedGCHandle => GCHandle.FromIntPtr (_gcHandle); | ||
|
|
||
| return handle; | ||
| } | ||
| } | ||
| #if DEBUG | ||
| static readonly HashSet<IntPtr> s_knownContexts = new (); | ||
|
|
||
| public static unsafe void EnsureAllContextsAreOurs (MarkCrossReferencesArgs* mcr) | ||
| { | ||
| lock (referenceTrackingHandles) { | ||
| lock (s_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 (!s_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) | ||
| { | ||
|
|
@@ -405,13 +385,17 @@ static void EnsureContextIsOurs (IntPtr context) | |
| 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); | ||
| lock (referenceTrackingHandles) { | ||
| referenceTrackingHandles [(IntPtr) context] = handle; | ||
| context->_gcHandle = GCHandle.ToIntPtr (handle); | ||
|
|
||
| #if DEBUG | ||
| lock (s_knownContexts) { | ||
| s_knownContexts.Add ((IntPtr)context); | ||
| } | ||
| #endif | ||
|
|
||
| return context; | ||
| } | ||
|
|
@@ -422,9 +406,11 @@ public static void Free (ref HandleContext* context) | |
| return; | ||
| } | ||
|
|
||
| lock (referenceTrackingHandles) { | ||
| referenceTrackingHandles.Remove ((IntPtr)context); | ||
| #if DEBUG | ||
| lock (s_knownContexts) { | ||
| s_knownContexts.Remove ((IntPtr)context); | ||
| } | ||
| #endif | ||
|
|
||
| NativeMemory.Free (context); | ||
| context = null; | ||
|
|
@@ -438,8 +424,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 | ||
| } | ||
simonrozsival marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| [UnmanagedCallersOnly] | ||
|
|
@@ -452,7 +439,9 @@ static unsafe void BridgeProcessingFinished (MarkCrossReferencesArgs* mcr) | |
| ReadOnlySpan<GCHandle> handlesToFree = ProcessCollectedContexts (mcr); | ||
| JavaMarshal.FinishCrossReferenceProcessing (mcr, handlesToFree); | ||
|
|
||
| bridgeProcessingSemaphore.Release (); | ||
| // 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<GCHandle> ProcessCollectedContexts (MarkCrossReferencesArgs* mcr) | ||
|
|
@@ -478,12 +467,12 @@ void ProcessContext (HandleContext* context) | |
| return; | ||
| } | ||
|
|
||
| GCHandle handle = HandleContext.GetAssociatedGCHandle (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); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.