Skip to content
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/Mono.Android/Android.Runtime/JNIEnvInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
146 changes: 68 additions & 78 deletions src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -22,15 +23,15 @@ 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;

public static ManagedValueManager Instance => s_instance.Value;

unsafe ManagedValueManager ()
{
Expand All @@ -41,31 +42,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);
}

Expand All @@ -75,7 +74,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--) {
Expand All @@ -86,7 +85,7 @@ void Remove (HandleContext* context)
}

if (peers.Count == 0) {
RegisteredInstances.Remove (key);
_registeredInstances.Remove (key);
}
}
}
Expand All @@ -95,9 +94,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);
Expand All @@ -107,11 +103,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;
}

Expand Down Expand Up @@ -160,8 +156,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--) {
Expand All @@ -173,7 +169,7 @@ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepVal
}

if (peers.Count == 0)
RegisteredInstances.Remove (key);
_registeredInstances.Remove (key);
}
return null;
}
Expand All @@ -182,28 +178,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);
}
}

Expand Down Expand Up @@ -284,9 +280,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)));
Expand All @@ -299,7 +295,7 @@ public override List<JniSurfacedPeerInfo> GetSurfacedPeers ()

unsafe struct ReferenceTrackingHandle : IDisposable
{
WeakReference<IJavaPeerable> _weakReference;
WeakReference<IJavaPeerable?> _weakReference;
HandleContext* _context;

public bool BelongsToContext (HandleContext* context)
Expand All @@ -308,7 +304,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
Expand All @@ -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) {
Expand All @@ -337,20 +333,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;
}
}

Expand All @@ -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<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)
{
Expand All @@ -405,13 +386,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;
}
Expand All @@ -422,9 +407,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;
Expand All @@ -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]
Expand All @@ -452,13 +440,15 @@ 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 (Instance.CollectPeers);
}

static unsafe ReadOnlySpan<GCHandle> ProcessCollectedContexts (MarkCrossReferencesArgs* mcr)
{
List<GCHandle> handlesToFree = [];
ManagedValueManager instance = GetOrCreateInstance ();
ManagedValueManager instance = Instance;

for (int i = 0; (nuint)i < mcr->ComponentCount; i++) {
StronglyConnectedComponent component = mcr->Components [i];
Expand All @@ -478,12 +468,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);
Expand Down
Loading