diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceFields.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceFields.cs index 43c33bdf95d..3533fef3708 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceFields.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceFields.cs @@ -15,11 +15,13 @@ internal JniInstanceFields (JniPeerMembers members) readonly JniPeerMembers Members; - readonly ConcurrentDictionary InstanceFields = new ConcurrentDictionary (1, 3, StringComparer.Ordinal); + ConcurrentDictionary? instanceFields; + + ConcurrentDictionary InstanceFields => GetOrCreate (ref instanceFields, 3); internal void Dispose () { - InstanceFields.Clear (); + Clear (ref instanceFields); } public JniFieldInfo GetFieldInfo (string encodedMember) diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs index 90ababdb74b..9f433d76b30 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs @@ -39,15 +39,16 @@ internal JniType JniPeerType { readonly Type DeclaringType; - readonly ConcurrentDictionary InstanceMethods = new ConcurrentDictionary (1, 3, StringComparer.Ordinal); - readonly ConcurrentDictionary SubclassConstructors = new ConcurrentDictionary (1, 1); + ConcurrentDictionary? instanceMethods; + ConcurrentDictionary? subclassConstructors; + + ConcurrentDictionary InstanceMethods => GetOrCreate (ref instanceMethods, 3); + ConcurrentDictionary SubclassConstructors => GetOrCreate (ref subclassConstructors, 1); internal void Dispose () { - InstanceMethods.Clear (); - foreach (var p in SubclassConstructors.Values) - p.Dispose (); - SubclassConstructors.Clear (); + Clear (ref instanceMethods); + Clear (ref subclassConstructors, static value => value.Dispose ()); if (jniPeerType != null) jniPeerType.Dispose (); diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticFields.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticFields.cs index f0c490460ab..2ddcf27af8a 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticFields.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticFields.cs @@ -15,7 +15,9 @@ internal JniStaticFields (JniPeerMembers members) readonly JniPeerMembers Members; - readonly ConcurrentDictionary StaticFields = new ConcurrentDictionary (1, 3, StringComparer.Ordinal); + ConcurrentDictionary? staticFields; + + ConcurrentDictionary StaticFields => GetOrCreate (ref staticFields, 3); public JniFieldInfo GetFieldInfo (string encodedMember) { @@ -28,7 +30,7 @@ public JniFieldInfo GetFieldInfo (string encodedMember) internal void Dispose () { - StaticFields.Clear (); + Clear (ref staticFields); } }} } diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs index 379d6f21c52..13004a8c3e6 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniStaticMethods.cs @@ -15,11 +15,13 @@ internal JniStaticMethods (JniPeerMembers members) internal readonly JniPeerMembers Members; - readonly ConcurrentDictionary StaticMethods = new ConcurrentDictionary (1, 3, StringComparer.Ordinal); + ConcurrentDictionary? staticMethods; + + ConcurrentDictionary StaticMethods => GetOrCreate (ref staticMethods, 3); internal void Dispose () { - StaticMethods.Clear (); + Clear (ref staticMethods); } public JniMethodInfo GetMethodInfo (string encodedMember) diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.cs index 1b64266f9c8..6efad57b4f1 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.cs @@ -1,9 +1,11 @@ #nullable enable using System; -using System.Diagnostics; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Reflection; +using System.Threading; namespace Java.Interop { @@ -108,6 +110,30 @@ static T Assert(T value) return value; } + static ConcurrentDictionary GetOrCreate (ref ConcurrentDictionary? dictionary, int capacity) + where TKey : notnull + { + var value = Volatile.Read (ref dictionary); + if (value != null) + return value; + + var candidate = new ConcurrentDictionary (1, capacity); + return Interlocked.CompareExchange (ref dictionary, candidate, null) ?? candidate; + } + + static void Clear (ref ConcurrentDictionary? dictionary, Action? dispose = null) + where TKey : notnull + { + var values = Interlocked.Exchange (ref dictionary, null); + if (values == null) + return; + if (dispose != null) { + foreach (var value in values.Values) + dispose (value); + } + values.Clear (); + } + protected virtual void Dispose (bool disposing) { if (!disposing || jniPeerType == null) diff --git a/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs b/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs index 45b2a3cb6e1..51a11362b45 100644 --- a/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs +++ b/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Reflection; +using System.Threading.Tasks; using Java.Interop; using NUnit.Framework; @@ -21,19 +22,148 @@ public void Ctor_CanReferenceNonexistentType () [Category ("TrimmableTypeMapUnsupported")] public void VirtualInvokeOnBaseInvokesMostDerivedJavaMethod () { - var registered = GetInstanceMethods (MyString._members.InstanceMethods); - Assert.AreEqual (0, registered.Count); + Assert.IsNull (GetInstanceMethods (MyString._members.InstanceMethods)); using (var s = new MyString ("hello!")) { + var registered = GetInstanceMethods (MyString._members.InstanceMethods); Assert.AreEqual (1, registered.Count); // for the constructor Assert.AreEqual ("hello!", s.ToString ()); Assert.AreEqual (1, registered.Count); } } + [Test] + [Category ("TrimmableTypeMapUnsupported")] + public void ConcurrentFirstUsePublishesSingleInstanceMethodCache () + { + var members = new JniPeerMembers (MyString.JniTypeName, typeof (MyString)); + try { + var methods = members.InstanceMethods; + var constructors = new JniMethodInfo [16]; + + Assert.IsNull (GetInstanceMethods (methods)); + Parallel.For (0, constructors.Length, i => constructors [i] = methods.GetConstructor ("()V")); + + var registered = GetInstanceMethods (methods); + Assert.AreEqual (1, registered.Count); + foreach (var constructor in constructors) + Assert.AreSame (constructors [0], constructor); + Assert.AreSame (registered ["()V"], constructors [0]); + } finally { + JniPeerMembers.Dispose (members); + } + } + + [Test] + public void PeerMemberCachesAreInitiallyNull () + { + var members = new JniPeerMembers (CallNonvirtualBase.JniTypeName, typeof (CallNonvirtualBase)); + try { + Assert.IsNull (GetInstanceFields (members.InstanceFields)); + Assert.IsNull (GetInstanceMethods (members.InstanceMethods)); + Assert.IsNull (GetSubclassConstructors (members.InstanceMethods)); + Assert.IsNull (GetStaticFields (members.StaticFields)); + Assert.IsNull (GetStaticMethods (members.StaticMethods)); + } finally { + JniPeerMembers.Dispose (members); + } + } + + [Test] + public void ConstructorTypeCacheIsAllocatedOnlyForManagedSubclasses () + { + var members = new JniPeerMembers (CallNonvirtualBase.JniTypeName, typeof (CallNonvirtualBase)); + try { + var methods = members.InstanceMethods; + + Assert.AreSame (methods, methods.GetConstructorsForType (typeof (CallNonvirtualBase))); + Assert.IsNull (GetSubclassConstructors (methods)); + + var derivedMethods = methods.GetConstructorsForType (typeof (CallNonvirtualDerived)); + var constructors = GetSubclassConstructors (methods); + Assert.AreEqual (1, constructors.Count); + Assert.AreSame (derivedMethods, constructors [typeof (CallNonvirtualDerived)]); + + methods.Dispose (); + Assert.IsNull (GetSubclassConstructors (methods)); + Assert.Throws (() => { + var type = derivedMethods.JniPeerType; + }); + } finally { + JniPeerMembers.Dispose (members); + } + } + + [Test] + public void ConcurrentFirstUsePublishesSingleFieldAndStaticMethodCaches () + { + var instanceMembers = new JniPeerMembers (CallNonvirtualBase.JniTypeName, typeof (CallNonvirtualBase)); + try { + var instanceFields = new JniFieldInfo [16]; + Assert.IsNull (GetInstanceFields (instanceMembers.InstanceFields)); + Parallel.For (0, instanceFields.Length, i => instanceFields [i] = instanceMembers.InstanceFields.GetFieldInfo ("methodInvoked.Z")); + AssertSingleCachedValue (GetInstanceFields (instanceMembers.InstanceFields), "methodInvoked.Z", instanceFields); + } finally { + JniPeerMembers.Dispose (instanceMembers); + } + + var staticMembers = new JniPeerMembers (JavaLangSystemTestObject.JniTypeName, typeof (JavaLangSystemTestObject)); + try { + var staticFields = new JniFieldInfo [16]; + Assert.IsNull (GetStaticFields (staticMembers.StaticFields)); + Parallel.For (0, staticFields.Length, i => staticFields [i] = staticMembers.StaticFields.GetFieldInfo ("in.Ljava/io/InputStream;")); + AssertSingleCachedValue (GetStaticFields (staticMembers.StaticFields), "in.Ljava/io/InputStream;", staticFields); + + var staticMethods = new JniMethodInfo [16]; + Assert.IsNull (GetStaticMethods (staticMembers.StaticMethods)); + Parallel.For (0, staticMethods.Length, i => staticMethods [i] = staticMembers.StaticMethods.GetMethodInfo ("currentTimeMillis.()J")); + AssertSingleCachedValue (GetStaticMethods (staticMembers.StaticMethods), "currentTimeMillis.()J", staticMethods); + } finally { + JniPeerMembers.Dispose (staticMembers); + } + } + + static void AssertSingleCachedValue (ConcurrentDictionary cache, string key, T [] values) + where T : class + { + Assert.AreEqual (1, cache.Count); + foreach (var value in values) + Assert.AreSame (values [0], value); + Assert.AreSame (cache [key], values [0]); + } + + static ConcurrentDictionary GetInstanceFields (JniPeerMembers.JniInstanceFields fields) + { + var field = typeof (JniPeerMembers.JniInstanceFields).GetField ("instanceFields", BindingFlags.NonPublic | BindingFlags.Instance); + return GetCache (field, fields); + } + static ConcurrentDictionary GetInstanceMethods (JniPeerMembers.JniInstanceMethods methods) { - var f = typeof (JniPeerMembers.JniInstanceMethods).GetField ("InstanceMethods", BindingFlags.NonPublic | BindingFlags.Instance); - return (ConcurrentDictionary) f.GetValue (methods); + var field = typeof (JniPeerMembers.JniInstanceMethods).GetField ("instanceMethods", BindingFlags.NonPublic | BindingFlags.Instance); + return GetCache (field, methods); + } + + static ConcurrentDictionary GetSubclassConstructors (JniPeerMembers.JniInstanceMethods methods) + { + var field = typeof (JniPeerMembers.JniInstanceMethods).GetField ("subclassConstructors", BindingFlags.NonPublic | BindingFlags.Instance); + return GetCache (field, methods); + } + + static ConcurrentDictionary GetStaticFields (JniPeerMembers.JniStaticFields fields) + { + var field = typeof (JniPeerMembers.JniStaticFields).GetField ("staticFields", BindingFlags.NonPublic | BindingFlags.Instance); + return GetCache (field, fields); + } + + static ConcurrentDictionary GetStaticMethods (JniPeerMembers.JniStaticMethods methods) + { + var field = typeof (JniPeerMembers.JniStaticMethods).GetField ("staticMethods", BindingFlags.NonPublic | BindingFlags.Instance); + return GetCache (field, methods); + } + + static ConcurrentDictionary GetCache (FieldInfo field, object owner) + { + return (ConcurrentDictionary) field.GetValue (owner); } [Test] @@ -168,6 +298,11 @@ public void DesugarInterfaceStaticMethod () } } + [JniTypeSignature (JniTypeName, GenerateJavaPeer=false)] + abstract class JavaLangSystemTestObject : JavaObject { + internal const string JniTypeName = "java/lang/System"; + } + [JniTypeSignature (JniTypeName, GenerateJavaPeer=false)] class MyString : JavaObject { internal const string JniTypeName = "java/lang/String";