diff --git a/src/Java.Runtime.Environment/Java.Interop/JavaBridgedValueManager.cs b/src/Java.Runtime.Environment/Java.Interop/JavaBridgedValueManager.cs new file mode 100644 index 000000000..0c17d51bc --- /dev/null +++ b/src/Java.Runtime.Environment/Java.Interop/JavaBridgedValueManager.cs @@ -0,0 +1,319 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Java; +using System.Threading; + +using Java.Interop; + +namespace System.Runtime.InteropServices.Java { + // https://github.com/dotnet/runtime/issues/115506 + + public struct ComponentCrossReference { + public nint SourceGroupIndex; + public nint DestinationGroupIndex; + } + + public unsafe struct StronglyConnectedComponent { + public int IsAlive; + public nint Count; + public System.IntPtr* Context; + } + + public unsafe struct MarkCrossReferences { + public nint ComponentsLen; + public StronglyConnectedComponent* Components; + public nint CrossReferencesLen; + public ComponentCrossReference* CrossReferences; + } + + static class JavaMarshal { + public static unsafe void Initialize(delegate* unmanaged markCrossReferences) => + throw null!; + + public static GCHandle CreateReferenceTrackingHandle(object obj, IntPtr context) => + throw null!; + + public static IntPtr GetContext(GCHandle obj) => + throw null!; + + public static unsafe void ReleaseMarkCrossReferenceResources(MarkCrossReferences* crossReferences) => + throw null!; + } +} + +namespace Java.Interop { + + class JavaBridgedValueManager : JniRuntime.JniValueManager + { + Dictionary>? RegisteredInstances = new (); + + internal unsafe JavaBridgedValueManager () + { + JavaMarshal.Initialize (&MarkCrossReferences); + } + + public override void WaitForGCBridgeProcessing () + { + } + + public override void CollectPeers () + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (ManagedValueManager)); + + var peers = new List (); + + lock (RegisteredInstances) { + foreach (var ps in RegisteredInstances.Values) { + foreach (var p in ps) { + peers.Add (p); + } + } + RegisteredInstances.Clear (); + } + List? exceptions = null; + foreach (var peer in peers) { + try { + if (peer.Target is IDisposable disposable) + disposable.Dispose (); + } + catch (Exception e) { + exceptions = exceptions ?? new List (); + exceptions.Add (e); + } + } + if (exceptions != null) + throw new AggregateException ("Exceptions while collecting peers.", exceptions); + } + + public override void AddPeer (IJavaPeerable value) + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (ManagedValueManager)); + + var r = value.PeerReference; + if (!r.IsValid) + throw new ObjectDisposedException (value.GetType ().FullName); + + if (r.Type != JniObjectReferenceType.Global) { + value.SetPeerReference (r.NewGlobalRef ()); + JniObjectReference.Dispose (ref r, JniObjectReferenceOptions.CopyAndDispose); + } + int key = value.JniIdentityHashCode; + lock (RegisteredInstances) { + List? peers; + if (!RegisteredInstances.TryGetValue (key, out peers)) { + peers = new List () { + CreateReferenceTrackingHandle (value) + }; + RegisteredInstances.Add (key, peers); + return; + } + + for (int i = peers.Count - 1; i >= 0; i--) { + var p = peers [i]; + if (p.Target is not IJavaPeerable peer) + continue; + if (!JniEnvironment.Types.IsSameObject (peer.PeerReference, value.PeerReference)) + continue; + if (Replaceable (p)) { + peers [i] = CreateReferenceTrackingHandle (value); + } else { + WarnNotReplacing (key, value, peer); + } + return; + } + peers.Add (CreateReferenceTrackingHandle (value)); + } + } + + static bool Replaceable (GCHandle handle) + { + if (handle.Target is not IJavaPeerable peer) + return true; + return peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Replaceable); + } + + void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepValue) + { + Runtime.ObjectReferenceManager.WriteGlobalReferenceLine ( + "Warning: Not registering PeerReference={0} IdentityHashCode=0x{1} Instance={2} Instance.Type={3} Java.Type={4}; " + + "keeping previously registered PeerReference={5} Instance={6} Instance.Type={7} Java.Type={8}.", + ignoreValue.PeerReference.ToString (), + key.ToString ("x", CultureInfo.InvariantCulture), + RuntimeHelpers.GetHashCode (ignoreValue).ToString ("x", CultureInfo.InvariantCulture), + ignoreValue.GetType ().FullName, + JniEnvironment.Types.GetJniTypeNameFromInstance (ignoreValue.PeerReference), + keepValue.PeerReference.ToString (), + RuntimeHelpers.GetHashCode (keepValue).ToString ("x", CultureInfo.InvariantCulture), + keepValue.GetType ().FullName, + JniEnvironment.Types.GetJniTypeNameFromInstance (keepValue.PeerReference)); + } + + public override IJavaPeerable? PeekPeer (JniObjectReference reference) + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (ManagedValueManager)); + + if (!reference.IsValid) + return null; + + int key = GetJniIdentityHashCode (reference); + + lock (RegisteredInstances) { + List? peers; + if (!RegisteredInstances.TryGetValue (key, out peers)) + return null; + + for (int i = peers.Count - 1; i >= 0; i--) { + var p = peers [i]; + if (p.Target is IJavaPeerable peer && JniEnvironment.Types.IsSameObject (reference, peer.PeerReference)) + return peer; + } + if (peers.Count == 0) + RegisteredInstances.Remove (key); + } + return null; + } + + public override void RemovePeer (IJavaPeerable value) + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (ManagedValueManager)); + + if (value == null) + throw new ArgumentNullException (nameof (value)); + + int key = value.JniIdentityHashCode; + lock (RegisteredInstances) { + List? peers; + if (!RegisteredInstances.TryGetValue (key, out peers)) + return; + + for (int i = peers.Count - 1; i >= 0; i--) { + var p = peers [i]; + if (object.ReferenceEquals (value, p.Target)) { + peers.RemoveAt (i); + FreeHandle (p); + } + } + if (peers.Count == 0) + RegisteredInstances.Remove (key); + } + } + + public override void FinalizePeer (IJavaPeerable value) + { + var h = value.PeerReference; + var o = Runtime.ObjectReferenceManager; + // MUST NOT use SafeHandle.ReferenceType: local refs are tied to a JniEnvironment + // and the JniEnvironment's corresponding thread; it's a thread-local value. + // Accessing SafeHandle.ReferenceType won't kill anything (so far...), but + // instead it always returns JniReferenceType.Invalid. + if (!h.IsValid || h.Type == JniObjectReferenceType.Local) { + if (o.LogGlobalReferenceMessages) { + o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}", + h.ToString (), + value.JniIdentityHashCode.ToString ("x", CultureInfo.InvariantCulture), + RuntimeHelpers.GetHashCode (value).ToString ("x", CultureInfo.InvariantCulture), + value.GetType ().ToString ()); + } + RemovePeer (value); + value.SetPeerReference (new JniObjectReference ()); + value.Finalized (); + return; + } + + RemovePeer (value); + if (o.LogGlobalReferenceMessages) { + o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}", + h.ToString (), + value.JniIdentityHashCode.ToString ("x", CultureInfo.InvariantCulture), + RuntimeHelpers.GetHashCode (value).ToString ("x", CultureInfo.InvariantCulture), + value.GetType ().ToString ()); + } + value.SetPeerReference (new JniObjectReference ()); + JniObjectReference.Dispose (ref h); + value.Finalized (); + } + + public override void ActivatePeer (IJavaPeerable? self, JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues) + { + try { + ActivateViaReflection (reference, cinfo, argumentValues); + } catch (Exception e) { + var m = string.Format ( + CultureInfo.InvariantCulture, + "Could not activate {{ PeerReference={0} IdentityHashCode=0x{1} Java.Type={2} }} for managed type '{3}'.", + reference, + GetJniIdentityHashCode (reference).ToString ("x", CultureInfo.InvariantCulture), + JniEnvironment.Types.GetJniTypeNameFromInstance (reference), + cinfo.DeclaringType?.FullName); + Debug.WriteLine (m); + + throw new NotSupportedException (m, e); + } + } + + void ActivateViaReflection (JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues) + { + var declType = GetDeclaringType (cinfo); + + #pragma warning disable IL2072 + var self = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (declType); + #pragma warning restore IL2072 + self.SetPeerReference (reference); + + cinfo.Invoke (self, argumentValues); + + [UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "🤷‍♂️")] + [return: DynamicallyAccessedMembers (Constructors)] + Type GetDeclaringType (ConstructorInfo cinfo) => + cinfo.DeclaringType ?? throw new NotSupportedException ("Do not know the type to create!"); + } + + public override List GetSurfacedPeers () + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (ManagedValueManager)); + + lock (RegisteredInstances) { + var peers = new List (RegisteredInstances.Count); + foreach (var e in RegisteredInstances) { + foreach (var p in e.Value) { + if (p.Target is not IJavaPeerable peer) + continue; + peers.Add (new JniSurfacedPeerInfo (e.Key, new WeakReference (peer))); + } + } + return peers; + } + } + + static GCHandle CreateReferenceTrackingHandle (IJavaPeerable value) => + JavaMarshal.CreateReferenceTrackingHandle (value, value.JniObjectReferenceControlBlock); + + static unsafe void FreeHandle (GCHandle handle) + { + IntPtr context = JavaMarshal.GetContext (handle); + NativeMemory.Free ((void*) context); + } + + [UnmanagedCallersOnly] + internal static unsafe void MarkCrossReferences (MarkCrossReferences* crossReferences) + { + // Java.Lang.JavaSystem.Gc (); + + JavaMarshal.ReleaseMarkCrossReferenceResources (crossReferences); + } + } +} diff --git a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs index 4cae1dc48..722e65365 100644 --- a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs +++ b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs @@ -14,7 +14,7 @@ enum GCBridgeUseWeakReferenceKind { Jni, } - class MonoRuntimeValueManager : JniRuntime.JniValueManager { + partial class MonoRuntimeValueManager : JniRuntime.JniValueManager { #pragma warning disable 0649 // This field is mutated by the java-interop native lib @@ -46,6 +46,10 @@ public override void OnSetRuntime (JniRuntime runtime) throw new NotSupportedException ("Could not register current AppDomain!"); if (JreNativeMethods.java_interop_gc_bridge_set_current_once (bridge) < 0) throw new NotSupportedException ("Could not set GC Bridge instance!"); + unsafe { + if (JreNativeMethods.java_interop_gc_bridge_set_mark_cross_references(bridge, &MarkCrossReferences) < 0) + throw new NotSupportedException("Could not set MarkCrossReferences!"); + } } catch (Exception) { JreNativeMethods.java_interop_gc_bridge_free (bridge); @@ -415,6 +419,18 @@ partial class JreNativeMethods { [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)] internal static extern void java_interop_gc_bridge_wait_for_bridge_processing (IntPtr bridge); + + [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)] + internal static extern unsafe int java_interop_gc_bridge_set_mark_cross_references ( + IntPtr bridge, + delegate* unmanaged[Cdecl] markCrossReferences + ); + + [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)] + internal static extern unsafe int java_interop_gc_bridge_release_mark_cross_references_resources ( + IntPtr bridge, + System.Runtime.InteropServices.Java.MarkCrossReferences* crossReferences + ); } sealed class OverrideStackTrace : Exception { diff --git a/src/java-interop/java-interop-gc-bridge-mono.cc b/src/java-interop/java-interop-gc-bridge-mono.cc index cebe8ece9..9014b2500 100644 --- a/src/java-interop/java-interop-gc-bridge-mono.cc +++ b/src/java-interop/java-interop-gc-bridge-mono.cc @@ -1288,6 +1288,7 @@ gc_cross_references (int num_sccs, MonoGCBridgeSCC **sccs, int num_xrefs, MonoGC static void managed_gc_cross_references (int num_sccs, MonoGCBridgeSCC **sccs, int num_xrefs, MonoGCBridgeXRef *xrefs) { + fprintf (stderr, "# jonp: managed_gc_cross_references: %d sccs and %d xrefs\n", num_sccs, num_xrefs); if (mono_bridge->mark_cross_references == NULL) { assert (!"mono_bridge->mark_cross_references is NULL; WE SHOULD NOT BE EXECUTING"); return; @@ -1317,12 +1318,14 @@ managed_gc_cross_references (int num_sccs, MonoGCBridgeSCC **sccs, int num_xrefs xref->DestinationGroupIndex = (void*) (intptr_t) xrefs [i].dst_scc_index; } + fprintf (stderr, "# jonp: calling managed mark_cross_references\n"); mono_bridge->mark_cross_references (&cross_references); for (i = 0; i < num_sccs; ++i) { Srij_StronglyConnectedComponent *scc = &cross_references.Components [i]; sccs [i]->is_alive = scc->IsAlive; } + fprintf (stderr, "# jonp: calling done\n"); } int