diff --git a/build-tools/automation/azure-pipelines.yaml b/build-tools/automation/azure-pipelines.yaml index 4799a033e..c907106f6 100644 --- a/build-tools/automation/azure-pipelines.yaml +++ b/build-tools/automation/azure-pipelines.yaml @@ -21,6 +21,7 @@ variables: DotNetCoreVersion: 3.1.300 HostedMacImage: macOS-10.15 HostedWinVS2019: Hosted Windows 2019 with VS2019 + NetCoreTargetFrameworkPathSuffix: -netcoreapp3.1 jobs: - job: windows_build diff --git a/build-tools/automation/templates/core-tests.yaml b/build-tools/automation/templates/core-tests.yaml index fad8361fe..8758f4677 100644 --- a/build-tools/automation/templates/core-tests.yaml +++ b/build-tools/automation/templates/core-tests.yaml @@ -64,7 +64,7 @@ steps: condition: eq('${{ parameters.runNativeTests }}', 'true') inputs: command: test - arguments: bin/Test$(Build.Configuration)/Java.Interop-Tests.dll + arguments: bin/Test$(Build.Configuration)$(NetCoreTargetFrameworkPathSuffix)/Java.Interop-Tests.dll continueOnError: true - task: DotNetCoreCLI@2 diff --git a/src/Java.Interop.Export/Java.Interop/MarshalMemberBuilder.cs b/src/Java.Interop.Export/Java.Interop/MarshalMemberBuilder.cs index 69919d646..1f5074027 100644 --- a/src/Java.Interop.Export/Java.Interop/MarshalMemberBuilder.cs +++ b/src/Java.Interop.Export/Java.Interop/MarshalMemberBuilder.cs @@ -370,7 +370,12 @@ static Expression GetRuntime () return Expression.Property (null, typeof (JniEnvironment), "Runtime"); } - static MethodInfo FormatterServices_GetUninitializedObject = Type.GetType ("System.Runtime.Serialization.FormatterServices", throwOnError: true) + static MethodInfo FormatterServices_GetUninitializedObject = +#if NETFRAMEWORK || NET_2_0 + typeof (System.Runtime.Serialization.FormatterServices) +#else // !(NETFRAMEWORK || NET_2_0) + typeof (System.Runtime.CompilerServices.RuntimeHelpers) +#endif // NETFRAMEWORK || NET_2_0 .GetRuntimeMethod ("GetUninitializedObject", new[]{typeof (Type)}); static MethodInfo IJavaPeerable_SetPeerReference = typeof (IJavaPeerable).GetRuntimeMethod ("SetPeerReference", new[]{typeof (JniObjectReference)}); diff --git a/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs b/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs index d26ef55bb..e77c9b916 100644 --- a/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs +++ b/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Diagnostics; using System.Collections.Generic; using System.Text; @@ -47,6 +48,8 @@ public static unsafe JniObjectReference FindClass (string classname) return r; } + // NativeMethods.java_interop_jnienv_exception_describe (info.EnvironmentPointer); + NativeMethods.java_interop_jnienv_exception_clear (info.EnvironmentPointer); var findClassThrown = new JniObjectReference (thrown, JniObjectReferenceType.Local); @@ -167,6 +170,17 @@ public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegi public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegistration [] methods, int numMethods) { +#if DEBUG && NETCOREAPP + foreach (var m in methods) { + if (m.Marshaler.GetType ().GenericTypeArguments.Length != 0) { + var method = m.Marshaler.Method; + Debug.WriteLine ($"JNIEnv::RegisterNatives() given a generic delegate type. .NET Core doesn't like this."); + Debug.WriteLine ($" Java: {m.Name}{m.Signature}"); + Debug.WriteLine ($" Marshaler Type={m.Marshaler.GetType ().FullName} Method={method.DeclaringType.FullName}.{method.Name}"); + } + } +#endif // DEBUG + int r = _RegisterNatives (type, methods, numMethods); if (r != 0) { diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index 03ac65994..91aef9673 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -74,9 +74,12 @@ protected virtual void Dispose (bool disposing) disposed = true; } + public abstract bool PeersRequireRelease {get;} + public abstract void WaitForGCBridgeProcessing (); public abstract void CollectPeers (); + public abstract void ReleasePeers (); public abstract void AddPeer (IJavaPeerable value); diff --git a/src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs b/src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs index c3ceb115d..57384f8d1 100644 --- a/src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs +++ b/src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs @@ -31,6 +31,9 @@ public class JreRuntimeOptions : JniRuntime.CreationOptions { public Collection ClassPath {get; private set;} + public TextWriter? JniGlobalReferenceLogWriter {get; set;} + public TextWriter? JniLocalReferenceLogWriter {get; set;} + public JreRuntimeOptions () { JniVersion = JniVersion.v1_2; @@ -39,16 +42,6 @@ public JreRuntimeOptions () Path.GetDirectoryName (typeof (JreRuntimeOptions).Assembly.Location), "java-interop.jar"), }; - - bool onMono = Type.GetType ("Mono.Runtime", throwOnError: false) != null; - if (onMono) { - ValueManager = ValueManager ?? new MonoRuntimeValueManager (); - ObjectReferenceManager = ObjectReferenceManager ?? new MonoRuntimeObjectReferenceManager (); - } - else { - ValueManager = ValueManager ?? new DummyValueManager (); - ObjectReferenceManager = ObjectReferenceManager ?? new DummyObjectReferenceManager (); - } } public JreRuntimeOptions AddOption (string option) @@ -87,12 +80,22 @@ static unsafe JreRuntimeOptions CreateJreVM (JreRuntimeOptions builder) if (builder == null) throw new ArgumentNullException ("builder"); + bool onMono = Type.GetType ("Mono.Runtime", throwOnError: false) != null; + if (onMono) { + builder.ValueManager = builder.ValueManager ?? new MonoRuntimeValueManager (); + builder.ObjectReferenceManager = builder.ObjectReferenceManager ?? new MonoRuntimeObjectReferenceManager (); + } + else { + builder.ValueManager = builder.ValueManager ?? new ManagedValueManager (); + builder.ObjectReferenceManager = builder.ObjectReferenceManager ?? new ManagedObjectReferenceManager (builder.JniGlobalReferenceLogWriter, builder.JniLocalReferenceLogWriter); + } + if (builder.InvocationPointer != IntPtr.Zero) return builder; if (!string.IsNullOrEmpty (builder.JvmLibraryPath)) { IntPtr errorPtr = IntPtr.Zero; - int r = NativeMethods.java_interop_jvm_load_with_error_message (builder.JvmLibraryPath, out errorPtr); + int r = NativeMethods.java_interop_jvm_load_with_error_message (builder.JvmLibraryPath!, out errorPtr); if (r != 0) { string error = Marshal.PtrToStringAnsi (errorPtr); NativeMethods.java_interop_free (errorPtr); @@ -166,52 +169,5 @@ partial class NativeMethods { [DllImport (JavaInteropLib, CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)] internal static extern int java_interop_jvm_create (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args); } - - class DummyValueManager : JniRuntime.JniValueManager { - - public override void WaitForGCBridgeProcessing () - { - } - - public override void CollectPeers () - { - } - - public override void AddPeer (IJavaPeerable reference) - { - } - - public override void RemovePeer (IJavaPeerable reference) - { - } - - public override void FinalizePeer (IJavaPeerable reference) - { - } - - public override List GetSurfacedPeers () - { - return null; - } - - public override IJavaPeerable PeekPeer (global::Java.Interop.JniObjectReference reference) - { - return null; - } - - public override void ActivatePeer (IJavaPeerable self, JniObjectReference reference, ConstructorInfo cinfo, object [] argumentValues) - { - } - } - - class DummyObjectReferenceManager : JniRuntime.JniObjectReferenceManager { - public override int GlobalReferenceCount { - get {return 0;} - } - - public override int WeakGlobalReferenceCount { - get {return 0;} - } - } } diff --git a/src/Java.Runtime.Environment/Java.Interop/ManagedObjectReferenceManager.cs b/src/Java.Runtime.Environment/Java.Interop/ManagedObjectReferenceManager.cs new file mode 100644 index 000000000..700c6f617 --- /dev/null +++ b/src/Java.Runtime.Environment/Java.Interop/ManagedObjectReferenceManager.cs @@ -0,0 +1,228 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +namespace Java.Interop { + + class ManagedObjectReferenceManager : JniRuntime.JniObjectReferenceManager { + + TextWriter? grefLog; + TextWriter? lrefLog; + + int grefCount; + int wgrefCount; + + + public override int GlobalReferenceCount => grefCount; + public override int WeakGlobalReferenceCount => wgrefCount; + + public override bool LogLocalReferenceMessages => lrefLog != null; + public override bool LogGlobalReferenceMessages => grefLog != null; + + public ManagedObjectReferenceManager (TextWriter? grefLog, TextWriter? lrefLog) + { + if (grefLog != null && lrefLog != null && object.ReferenceEquals (grefLog, lrefLog)) { + this.grefLog = this.lrefLog = TextWriter.Synchronized (grefLog); + return; + } + + var grefPath = Environment.GetEnvironmentVariable ("JAVA_INTEROP_GREF_LOG"); + var lrefPath = Environment.GetEnvironmentVariable ("JAVA_INTEROP_LREF_LOG"); + + bool samePath = !string.IsNullOrEmpty (grefPath) && + !string.IsNullOrEmpty (lrefPath) && + grefPath == lrefPath; + + if (grefLog != null) { + this.grefLog = TextWriter.Synchronized (grefLog); + } + if (lrefLog != null) { + this.lrefLog = TextWriter.Synchronized (lrefLog); + } + + if (this.grefLog == null && !string.IsNullOrEmpty (grefPath)) { + this.grefLog = TextWriter.Synchronized (CreateTextWriter (grefPath)); + } + if (this.lrefLog == null && samePath) { + this.lrefLog = this.grefLog; + } + if (this.lrefLog == null && !string.IsNullOrEmpty (lrefPath)) { + this.lrefLog = TextWriter.Synchronized (CreateTextWriter (lrefPath)); + } + } + + public override void OnSetRuntime (JniRuntime runtime) + { + base.OnSetRuntime (runtime); + } + + static TextWriter? CreateTextWriter (string path) + { + return new StreamWriter (path, append: false, encoding: new UTF8Encoding (encoderShouldEmitUTF8Identifier: false)); + } + + public override void WriteLocalReferenceLine (string format, params object[] args) + { + if (lrefLog == null) + return; + lrefLog.WriteLine (format, args); + lrefLog.Flush (); + } + + public override JniObjectReference CreateLocalReference (JniObjectReference reference, ref int localReferenceCount) + { + if (!reference.IsValid) + return reference; + + var r = base.CreateLocalReference (reference, ref localReferenceCount); + + CreatedReference (lrefLog, "+l+ lrefc", localReferenceCount, reference, r, Runtime); + + return r; + } + + public override void DeleteLocalReference (ref JniObjectReference reference, ref int localReferenceCount) + { + if (!reference.IsValid) + return; + + var r = reference; + + base.DeleteLocalReference (ref reference, ref localReferenceCount); + + DeletedReference (lrefLog, "-l- lrefc", localReferenceCount, r, Runtime); + } + + public override void CreatedLocalReference (JniObjectReference reference, ref int localReferenceCount) + { + if (!reference.IsValid) + return; + base.CreatedLocalReference (reference, ref localReferenceCount); + CreatedReference (lrefLog, "+l+ lrefc", localReferenceCount, reference, Runtime); + } + + public override IntPtr ReleaseLocalReference (ref JniObjectReference reference, ref int localReferenceCount) + { + if (!reference.IsValid) + return IntPtr.Zero; + var r = reference; + var p = base.ReleaseLocalReference (ref reference, ref localReferenceCount); + DeletedReference (lrefLog, "-l- lrefc", localReferenceCount, r, Runtime); + return p; + } + + public override void WriteGlobalReferenceLine (string format, params object?[]? args) + { + if (grefLog == null) + return; + grefLog.WriteLine (format, args); + grefLog.Flush (); + } + + public override JniObjectReference CreateGlobalReference (JniObjectReference reference) + { + if (!reference.IsValid) + return reference; + var n = base.CreateGlobalReference (reference); + int c = Interlocked.Increment (ref grefCount); + CreatedReference (grefLog, "+g+ grefc", c, reference, n, Runtime); + return n; + } + + public override void DeleteGlobalReference (ref JniObjectReference reference) + { + if (!reference.IsValid) + return; + int c = Interlocked.Decrement (ref grefCount); + DeletedReference (grefLog, "-g- grefc", c, reference, Runtime); + base.DeleteGlobalReference (ref reference); + } + + public override JniObjectReference CreateWeakGlobalReference (JniObjectReference reference) + { + if (!reference.IsValid) + return reference; + var n = base.CreateWeakGlobalReference (reference); + + int wc = Interlocked.Increment (ref wgrefCount); + int gc = grefCount; + if (grefLog != null) { + string message = $"+w+ grefc {gc} gwrefc {wc} obj-handle {reference.ToString ()} -> new-handle {n.ToString ()} " + + $"from thread '{Runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" + + Environment.NewLine + + Runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); + grefLog.WriteLine (message); + grefLog.Flush (); + } + + return n; + } + + public override void DeleteWeakGlobalReference (ref JniObjectReference reference) + { + if (!reference.IsValid) + return; + + int wc = Interlocked.Decrement (ref wgrefCount); + int gc = grefCount; + + if (grefLog != null) { + string message = $"-w- grefc {gc} gwrefc {wc} handle {reference.ToString ()} " + + $"from thread '{Runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" + + Environment.NewLine + + Runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); + grefLog.WriteLine (message); + grefLog.Flush (); + } + + base.DeleteWeakGlobalReference (ref reference); + } + + protected override void Dispose (bool disposing) + { + } + + static void CreatedReference (TextWriter? writer, string kind, int count, JniObjectReference reference, JniRuntime runtime) + { + if (writer == null) + return; + string message = $"{kind} {count} handle {reference.ToString ()} " + + $"from thread '{runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" + + Environment.NewLine + + runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); + writer.WriteLine (message); + writer.Flush (); + } + + static void CreatedReference (TextWriter? writer, string kind, int count, JniObjectReference reference, JniObjectReference newReference, JniRuntime runtime) + { + if (writer == null) + return; + string message = $"{kind} {count} obj-handle {reference.ToString ()} -> new-handle {newReference.ToString ()} " + + $"from thread '{runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" + + Environment.NewLine + + runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); + writer.WriteLine (message); + writer.Flush (); + } + + static void DeletedReference (TextWriter? writer, string kind, int count, JniObjectReference reference, JniRuntime runtime) + { + if (writer == null) + return; + string message = $"{kind} {count} handle {reference.ToString ()} " + + $"from thread '{runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" + + Environment.NewLine + + runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); + writer.WriteLine (message); + writer.Flush (); + } + } +} diff --git a/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs b/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs new file mode 100644 index 000000000..0430b166b --- /dev/null +++ b/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Java.Interop { + + class ManagedValueManager : JniRuntime.JniValueManager { + + Dictionary>? RegisteredInstances = new Dictionary>(); + + public override bool PeersRequireRelease => true; + + public override void WaitForGCBridgeProcessing () + { + } + + public override void CollectPeers () + { + } + + public override void ReleasePeers () + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); + + var peers = new List (); + + lock (RegisteredInstances) { + foreach (var ps in RegisteredInstances.Values) { + foreach (var p in ps) { + peers.Add (p); + } + } + RegisteredInstances.Clear (); + } + foreach (var peer in peers) { + var r = peer.PeerReference; + peer.SetPeerReference (new JniObjectReference ()); + JniObjectReference.Dispose (ref r); + } + } + + public override void AddPeer (IJavaPeerable value) + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); + + var r = value.PeerReference; + if (!r.IsValid) + throw new ObjectDisposedException (value.GetType ().FullName); + var o = PeekPeer (value.PeerReference); + if (o != null) + return; + + 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 () { + value, + }; + RegisteredInstances.Add (key, peers); + return; + } + + for (int i = peers.Count - 1; i >= 0; i--) { + var p = peers [i]; + if (!JniEnvironment.Types.IsSameObject (p.PeerReference, value.PeerReference)) + continue; + if (Replaceable (p)) { + peers [i] = value; + } else { + WarnNotReplacing (key, value, p); + } + return; + } + peers.Add (value); + } + } + + static bool Replaceable (IJavaPeerable peer) + { + if (peer == null) + return true; + return (peer.JniManagedPeerState & JniManagedPeerStates.Replaceable) == 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"), + RuntimeHelpers.GetHashCode (ignoreValue).ToString ("x"), + ignoreValue.GetType ().FullName, + JniEnvironment.Types.GetJniTypeNameFromInstance (ignoreValue.PeerReference), + keepValue.PeerReference.ToString (), + RuntimeHelpers.GetHashCode (keepValue).ToString ("x"), + keepValue.GetType ().FullName, + JniEnvironment.Types.GetJniTypeNameFromInstance (keepValue.PeerReference)); + } + + public override IJavaPeerable? PeekPeer (JniObjectReference reference) + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); + + 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 (JniEnvironment.Types.IsSameObject (reference, p.PeerReference)) + return p; + } + if (peers.Count == 0) + RegisteredInstances.Remove (key); + } + return null; + } + + public override void RemovePeer (IJavaPeerable value) + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); + + 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)) { + peers.RemoveAt (i); + } + } + 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"), + RuntimeHelpers.GetHashCode (value).ToString ("x"), + 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"), + RuntimeHelpers.GetHashCode (value).ToString ("x"), + 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) + { + Console.WriteLine ($"# jonp: activating reference={reference} for {cinfo.DeclaringType.FullName}"); + var runtime = JniEnvironment.Runtime; + + try { + var f = runtime.MarshalMemberBuilder.CreateConstructActivationPeerFunc (cinfo); + f (cinfo, reference, argumentValues); + } catch (Exception e) { + var m = string.Format ("Could not activate {{ PeerReference={0} IdentityHashCode=0x{1} Java.Type={2} }} for managed type '{3}'.", + reference, + runtime.ValueManager.GetJniIdentityHashCode (reference).ToString ("x"), + JniEnvironment.Types.GetJniTypeNameFromInstance (reference), + cinfo.DeclaringType.FullName); + Debug.WriteLine (m); + + throw new NotSupportedException (m, e); + } + } + + public override List GetSurfacedPeers () + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); + + lock (RegisteredInstances) { + var peers = new List (RegisteredInstances.Count); + foreach (var e in RegisteredInstances) { + foreach (var p in e.Value) { + peers.Add (new JniSurfacedPeerInfo (e.Key, new WeakReference (p))); + } + } + return peers; + } + } + } +} diff --git a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeObjectReferenceManager.cs b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeObjectReferenceManager.cs index cf071e94d..68025e066 100644 --- a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeObjectReferenceManager.cs +++ b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeObjectReferenceManager.cs @@ -58,14 +58,14 @@ public override JniObjectReference CreateLocalReference (JniObjectReference refe return r; } - string GetCurrentManagedThreadName (bool create) + string? GetCurrentManagedThreadName (bool create) { if (create) return Runtime.GetCurrentManagedThreadName (); return null; } - string GetCurrentManagedThreadStack (bool create) + string? GetCurrentManagedThreadStack (bool create) { if (create) return Runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); @@ -120,7 +120,7 @@ public override bool LogGlobalReferenceMessages { get {return logGlobalRefs;} } - public override void WriteGlobalReferenceLine (string format, params object[] args) + public override void WriteGlobalReferenceLine (string format, params object?[]? args) { if (!LogGlobalReferenceMessages) return; @@ -217,13 +217,13 @@ partial class NativeMethods { internal static extern int java_interop_gc_bridge_lref_set_log_level (IntPtr bridge, int level); [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)] - internal static extern void java_interop_gc_bridge_lref_log_message (IntPtr bridge, int level, string message); + internal static extern void java_interop_gc_bridge_lref_log_message (IntPtr bridge, int level, string? message); [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)] - internal static extern void java_interop_gc_bridge_lref_log_new (IntPtr bridge, int lref_count, IntPtr curHandle, byte curType, IntPtr newHandle, byte newType, string thread_name, long thread_id, string from); + internal static extern void java_interop_gc_bridge_lref_log_new (IntPtr bridge, int lref_count, IntPtr curHandle, byte curType, IntPtr newHandle, byte newType, string? thread_name, long thread_id, string? from); [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)] - internal static extern void java_interop_gc_bridge_lref_log_delete (IntPtr bridge, int lref_count, IntPtr handle, byte type, string thread_name, long thread_id, string from); + internal static extern void java_interop_gc_bridge_lref_log_delete (IntPtr bridge, int lref_count, IntPtr handle, byte type, string? thread_name, long thread_id, string? from); [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)] internal static extern IntPtr java_interop_gc_bridge_gref_get_log_file (IntPtr bridge); @@ -232,19 +232,19 @@ partial class NativeMethods { internal static extern int java_interop_gc_bridge_gref_set_log_level (IntPtr bridge, int level); [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)] - internal static extern void java_interop_gc_bridge_gref_log_message (IntPtr bridge, int level, string message); + internal static extern void java_interop_gc_bridge_gref_log_message (IntPtr bridge, int level, string? message); [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)] - internal static extern int java_interop_gc_bridge_gref_log_new (IntPtr bridge, IntPtr curHandle, byte curType, IntPtr newHandle, byte newType, string thread_name, long thread_id, string from); + internal static extern int java_interop_gc_bridge_gref_log_new (IntPtr bridge, IntPtr curHandle, byte curType, IntPtr newHandle, byte newType, string? thread_name, long thread_id, string? from); [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)] - internal static extern int java_interop_gc_bridge_gref_log_delete (IntPtr bridge, IntPtr handle, byte type, string thread_name, long thread_id, string from); + internal static extern int java_interop_gc_bridge_gref_log_delete (IntPtr bridge, IntPtr handle, byte type, string? thread_name, long thread_id, string? from); [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)] - internal static extern int java_interop_gc_bridge_weak_gref_log_new (IntPtr bridge, IntPtr curHandle, byte curType, IntPtr newHandle, byte newType, string thread_name, long thread_id, string from); + internal static extern int java_interop_gc_bridge_weak_gref_log_new (IntPtr bridge, IntPtr curHandle, byte curType, IntPtr newHandle, byte newType, string? thread_name, long thread_id, string? from); [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)] - internal static extern int java_interop_gc_bridge_weak_gref_log_delete (IntPtr bridge, IntPtr handle, byte type, string thread_name, long thread_id, string from); + internal static extern int java_interop_gc_bridge_weak_gref_log_delete (IntPtr bridge, IntPtr handle, byte type, string? thread_name, long thread_id, string? from); } } diff --git a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs index 169ba9c06..48502c46e 100644 --- a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs +++ b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs @@ -23,6 +23,8 @@ class MonoRuntimeValueManager : JniRuntime.JniValueManager { IntPtr bridge; + public override bool PeersRequireRelease => false; + public override void OnSetRuntime (JniRuntime runtime) { base.OnSetRuntime (runtime); @@ -68,6 +70,30 @@ public override void CollectPeers () GC.Collect (); } + public override void ReleasePeers () + { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); + + var peers = new List (); + + lock (RegisteredInstances) { + foreach (var wpeers in RegisteredInstances.Values) { + foreach (var wpeer in wpeers) { + if (!wpeer.TryGetTarget (out var peer)) + continue; + peers.Add (peer); + } + } + RegisteredInstances.Clear (); + } + foreach (var peer in peers) { + var r = peer.PeerReference; + peer.SetPeerReference (new JniObjectReference ()); + JniObjectReference.Dispose (ref r); + } + } + protected override void Dispose (bool disposing) { base.Dispose (disposing); @@ -97,11 +123,14 @@ protected override void Dispose (bool disposing) } } - Dictionary>> RegisteredInstances = new Dictionary>>(); + Dictionary>>? RegisteredInstances = new Dictionary>>(); public override List GetSurfacedPeers () { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); + lock (RegisteredInstances) { var peers = new List (RegisteredInstances.Count); foreach (var e in RegisteredInstances) { @@ -115,6 +144,9 @@ public override List GetSurfacedPeers () public override void AddPeer (IJavaPeerable value) { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); + var r = value.PeerReference; if (!r.IsValid) throw new ObjectDisposedException (value.GetType ().FullName); @@ -183,6 +215,9 @@ static bool Replaceable (IJavaPeerable peer) public override void RemovePeer (IJavaPeerable value) { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); + if (value == null) throw new ArgumentNullException (nameof (value)); @@ -209,8 +244,11 @@ public override void RemovePeer (IJavaPeerable value) } } - public override IJavaPeerable PeekPeer (JniObjectReference reference) + public override IJavaPeerable? PeekPeer (JniObjectReference reference) { + if (RegisteredInstances == null) + throw new ObjectDisposedException (nameof (MonoRuntimeValueManager)); + if (!reference.IsValid) return null; @@ -245,7 +283,7 @@ static Exception CreateJniLocationException () } } - public override void ActivatePeer (IJavaPeerable self, JniObjectReference reference, ConstructorInfo cinfo, object [] argumentValues) + public override void ActivatePeer (IJavaPeerable? self, JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues) { var runtime = JniEnvironment.Runtime; @@ -337,19 +375,19 @@ internal protected virtual bool TryGC (IJavaPeerable value, ref JniObjectReferen } static class JavaLangRuntime { - static JniType _typeRef; + static JniType? _typeRef; static JniType TypeRef { get {return JniType.GetCachedJniType (ref _typeRef, "java/lang/Runtime");} } - static JniMethodInfo _getRuntime; + static JniMethodInfo? _getRuntime; internal static JniObjectReference GetRuntime () { TypeRef.GetCachedStaticMethod (ref _getRuntime, "getRuntime", "()Ljava/lang/Runtime;"); return JniEnvironment.StaticMethods.CallStaticObjectMethod (TypeRef.PeerReference, _getRuntime); } - static JniMethodInfo _gc; + static JniMethodInfo? _gc; internal static void GC (JniObjectReference runtime) { TypeRef.GetCachedInstanceMethod (ref _gc, "gc", "()V"); diff --git a/src/Java.Runtime.Environment/Java.Runtime.Environment.csproj b/src/Java.Runtime.Environment/Java.Runtime.Environment.csproj index b1440bad1..5f4ce04d4 100644 --- a/src/Java.Runtime.Environment/Java.Runtime.Environment.csproj +++ b/src/Java.Runtime.Environment/Java.Runtime.Environment.csproj @@ -5,6 +5,8 @@ true ..\..\product.snk true + 8.0 + enable diff --git a/tests/Java.Interop-Tests/Java.Interop-Tests.csproj b/tests/Java.Interop-Tests/Java.Interop-Tests.csproj index 891d80447..e7688578f 100644 --- a/tests/Java.Interop-Tests/Java.Interop-Tests.csproj +++ b/tests/Java.Interop-Tests/Java.Interop-Tests.csproj @@ -10,6 +10,10 @@ $(TestOutputFullPath) + + NO_MARSHAL_MEMBER_BUILDER_SUPPORT + + @@ -33,6 +37,10 @@ + + + + diff --git a/tests/Java.Interop-Tests/Java.Interop/JavaObjectTest.cs b/tests/Java.Interop-Tests/Java.Interop/JavaObjectTest.cs index 6ce8935f0..04348ef6c 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JavaObjectTest.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JavaObjectTest.cs @@ -75,6 +75,9 @@ public void RegisterWithVM_PermitsAliases () [Test] public void UnreferencedInstanceIsCollected () { + if (JniEnvironment.Runtime.ValueManager.PeersRequireRelease) + Assert.Ignore (); + JniObjectReference oldHandle = new JniObjectReference (); WeakReference r = null; var t = new Thread (() => { @@ -107,6 +110,9 @@ public void Dispose () [Test] public void Dispose_Finalized () { + if (JniEnvironment.Runtime.ValueManager.PeersRequireRelease) + Assert.Ignore (); + var d = false; var f = false; var t = new Thread (() => { diff --git a/tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs b/tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs index ad51db4fd..e1a816c6b 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs @@ -15,7 +15,9 @@ static partial void CreateJavaVM () var c = new TestJVM ( jars: new[]{ "interop-test.jar" }, typeMappings: new Dictionary () { +#if !NO_MARSHAL_MEMBER_BUILDER_SUPPORT { TestType.JniTypeName, typeof (TestType) }, +#endif // !NO_MARSHAL_MEMBER_BUILDER_SUPPORT { GenericHolder.JniTypeName, typeof (GenericHolder<>) }, } ); diff --git a/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniValueManagerTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniValueManagerTests.cs index f8f8c42bb..8e6b2b9d3 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniValueManagerTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniRuntime.JniValueManagerTests.cs @@ -31,6 +31,8 @@ public void CreateValue () class MyValueManager : JniRuntime.JniValueManager { + public override bool PeersRequireRelease => true; + public override void WaitForGCBridgeProcessing () { } @@ -39,6 +41,10 @@ public override void CollectPeers () { } + public override void ReleasePeers () + { + } + public override void AddPeer (IJavaPeerable reference) { } diff --git a/tests/Java.Interop-Tests/Java.Interop/JniRuntimeTest.cs b/tests/Java.Interop-Tests/Java.Interop/JniRuntimeTest.cs index 5503dcbf6..33800d376 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniRuntimeTest.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniRuntimeTest.cs @@ -130,6 +130,8 @@ public override int WeakGlobalReferenceCount { class ProxyValueManager : JniValueManager { + public override bool PeersRequireRelease => throw new NotImplementedException (); + public override void AddPeer (IJavaPeerable peer) { } @@ -138,6 +140,10 @@ public override void CollectPeers () { } + public override void ReleasePeers () + { + } + public override void FinalizePeer (IJavaPeerable peer) { } diff --git a/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs b/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs index a7c83978e..08e878688 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniValueMarshalerContractTests.cs @@ -645,7 +645,7 @@ public DemoValueTypeValueMarshaler () public override DemoValueType CreateGenericValue (ref JniObjectReference reference, JniObjectReferenceOptions options, Type targetType) { - var v = Int32Marshaler.CreateGenericValue (ref reference, options, targetType); + var v = Int32Marshaler.CreateGenericValue (ref reference, options, typeof (int)); return new DemoValueType (v); } diff --git a/tests/Java.Interop-Tests/Java.Interop/TestType.cs b/tests/Java.Interop-Tests/Java.Interop/TestType.cs index 4640d5f14..7e910a4fb 100644 --- a/tests/Java.Interop-Tests/Java.Interop/TestType.cs +++ b/tests/Java.Interop-Tests/Java.Interop/TestType.cs @@ -109,10 +109,11 @@ public bool PropogateFinallyBlockExecuted { static Delegate GetEqualsThisHandler () { - Func h = _EqualsThis; + EqualsThisMarshalMethod h = _EqualsThis; return JniEnvironment.Runtime.MarshalMemberBuilder.CreateMarshalToManagedDelegate (h); } + delegate bool EqualsThisMarshalMethod (IntPtr jnienv, IntPtr n_self, IntPtr n_value); static bool _EqualsThis (IntPtr jnienv, IntPtr n_self, IntPtr n_value) { var jvm = JniEnvironment.Runtime; @@ -131,10 +132,11 @@ static bool _EqualsThis (IntPtr jnienv, IntPtr n_self, IntPtr n_value) static Delegate GetInt32ValueHandler () { - Func h = _GetInt32Value; + GetInt32ValueMarshalMethod h = _GetInt32Value; return JniEnvironment.Runtime.MarshalMemberBuilder.CreateMarshalToManagedDelegate (h); } + delegate int GetInt32ValueMarshalMethod (IntPtr jnienv, IntPtr n_self); static int _GetInt32Value (IntPtr jnienv, IntPtr n_self) { var r_self = new JniObjectReference (n_self); @@ -148,10 +150,11 @@ static int _GetInt32Value (IntPtr jnienv, IntPtr n_self) static Delegate _GetStringValueHandler () { - Func h = GetStringValueHandler; + GetStringValueMarshalMethod h = GetStringValueHandler; return JniEnvironment.Runtime.MarshalMemberBuilder.CreateMarshalToManagedDelegate (h); } + delegate IntPtr GetStringValueMarshalMethod (IntPtr jnienv, IntPtr n_self, int value); static IntPtr GetStringValueHandler (IntPtr jnienv, IntPtr n_self, int value) { var r_self = new JniObjectReference (n_self); @@ -171,10 +174,11 @@ static IntPtr GetStringValueHandler (IntPtr jnienv, IntPtr n_self, int value) static Delegate GetMethodThrowsHandler () { - Action h = MethodThrowsHandler; + MethodThrowsMarshalMethod h = MethodThrowsHandler; return JniEnvironment.Runtime.MarshalMemberBuilder.CreateMarshalToManagedDelegate (h); } + delegate void MethodThrowsMarshalMethod (IntPtr jnienv, IntPtr n_self); static void MethodThrowsHandler (IntPtr jnienv, IntPtr n_self) { var r_self = new JniObjectReference (n_self); diff --git a/tests/Java.Interop.Export-Tests/Java.Interop/MarshalMemberBuilderTest.cs b/tests/Java.Interop.Export-Tests/Java.Interop/MarshalMemberBuilderTest.cs index 4aa607aa4..0202c33db 100644 --- a/tests/Java.Interop.Export-Tests/Java.Interop/MarshalMemberBuilderTest.cs +++ b/tests/Java.Interop.Export-Tests/Java.Interop/MarshalMemberBuilderTest.cs @@ -539,7 +539,7 @@ public void CreateConstructActivationPeerExpression () object self; type = constructor.DeclaringType; - self = FormatterServices.GetUninitializedObject(type); + self = RuntimeHelpers.GetUninitializedObject(type); (IJavaPeerable)self.SetPeerReference(reference); constructor.Invoke(self, parameters); return self; diff --git a/tests/TestJVM/TestJVM.cs b/tests/TestJVM/TestJVM.cs index fa61be91b..8c0ff44cd 100644 --- a/tests/TestJVM/TestJVM.cs +++ b/tests/TestJVM/TestJVM.cs @@ -3,7 +3,9 @@ using System.IO; using System.Diagnostics; using System.Linq; +using System.Reflection; using System.Threading; +using System.Text; using Xamarin.Android.Tools; @@ -13,12 +15,14 @@ namespace Java.InteropTests { public class TestJVM : JreRuntime { - static JreRuntimeOptions CreateBuilder (string[] jars) + static JreRuntimeOptions CreateBuilder (string[] jars, Assembly caller) { var dir = Path.GetDirectoryName (typeof (TestJVM).Assembly.Location); var builder = new JreRuntimeOptions () { JvmLibraryPath = GetJvmLibraryPath (), JniAddNativeMethodRegistrationAttributePresent = true, + JniGlobalReferenceLogWriter = GetLogOutput ("JAVA_INTEROP_GREF_LOG", "g-", caller), + JniLocalReferenceLogWriter = GetLogOutput ("JAVA_INTEROP_LREF_LOG", "l-", caller), }; if (jars != null) { foreach (var jar in jars) @@ -30,6 +34,17 @@ static JreRuntimeOptions CreateBuilder (string[] jars) return builder; } + static TextWriter GetLogOutput (string envVar, string prefix, Assembly caller) + { + var path = Environment.GetEnvironmentVariable (envVar); + if (!string.IsNullOrEmpty (path)) + return null; + path = Path.Combine ( + Path.GetDirectoryName (typeof (TestJVM).Assembly.Location), + prefix + Path.GetFileName (caller.Location) + ".txt"); + return new StreamWriter (path, append: false, encoding: new UTF8Encoding (encoderShouldEmitUTF8Identifier: false)); + } + static string GetJvmLibraryPath () { var env = Environment.GetEnvironmentVariable ("JI_JVM_PATH"); @@ -43,7 +58,7 @@ static string GetJvmLibraryPath () Dictionary typeMappings; public TestJVM (string[] jars = null, Dictionary typeMappings = null) - : base (CreateBuilder (jars)) + : base (CreateBuilder (jars, Assembly.GetCallingAssembly ())) { this.typeMappings = typeMappings; }