From 7436bbbf410e0232e09d8b83ced177fbe05e43ce Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 8 Apr 2021 16:31:39 -0400 Subject: [PATCH 1/3] Add MetadataUpdateHandlerAttribute And at least the beginning of reflection cache clearing support. --- .../System.Private.CoreLib.csproj | 1 + .../RuntimeTypeMetadataUpdateHandler.cs | 25 +++++++++++ .../src/System/RuntimeType.CoreCLR.cs | 31 +++++++++++++ .../System.Private.CoreLib.Shared.projitems | 1 + .../MetadataUpdateHandlerAttribute.cs | 20 +++++++++ .../ref/System.Runtime.Loader.cs | 6 +++ .../MetadataUpdateHandlerAttributeTest.cs | 18 ++++++++ .../tests/System.Runtime.Loader.Tests.csproj | 1 + .../tests/System.Runtime.Tests.csproj | 1 + .../System/Reflection/ReflectionCacheTests.cs | 43 +++++++++++++++++++ 10 files changed, 147 insertions(+) create mode 100644 src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdateHandlerAttribute.cs create mode 100644 src/libraries/System.Runtime.Loader/tests/MetadataUpdateHandlerAttributeTest.cs create mode 100644 src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 5f0d5c2c940f87..d841ffc7287cbb 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -199,6 +199,7 @@ + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs new file mode 100644 index 00000000000000..25f29ed8e596eb --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/RuntimeTypeMetadataUpdateHandler.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection.Metadata; + +[assembly: MetadataUpdateHandler(typeof(RuntimeTypeMetadataUpdateHandler))] + +namespace System.Reflection.Metadata +{ + /// Metadata update handler used to clear a Type's reflection cache in response to a metadata update notification. + internal static class RuntimeTypeMetadataUpdateHandler + { + public static void BeforeUpdate(Type? type) + { + if (type is RuntimeType rt) + { + rt.ClearCache(); + } + + // TODO: https://github.com/dotnet/runtime/issues/50938 + // Do we need to clear the cache on other types, e.g. ones derived from this one? + // Do we need to clear a cache on any other kinds of types? + } + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index a1d174667db10f..a6d28b77126915 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -2418,6 +2418,37 @@ private RuntimeTypeCache InitializeCache() return cache; } + internal void ClearCache() + { + // If there isn't a GCHandle yet, there's nothing more to do. + if (Volatile.Read(ref m_cache) == IntPtr.Zero) + { + return; + } + + // Loop until the cache is successfully zero'd out. + do + { + // If the GCHandle doesn't wrap a cache yet, there's nothing more to do. + RuntimeTypeCache? existingCache = (RuntimeTypeCache?)GCHandle.InternalGet(m_cache); + if (existingCache is null) + { + return; + } + + // Create a new, empty cache to replace the old one and try to substitute it in. + var newCache = new RuntimeTypeCache(this); + if (ReferenceEquals(GCHandle.InternalCompareExchange(m_cache, newCache, existingCache), existingCache)) + { + // We were successful, so there's nothing more to do. + return; + } + + // We raced with someone else to initialize the cache. Try again. + } + while (true); + } + private string? GetDefaultMemberName() { return Cache.GetDefaultMemberName(); diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 9256e41f8a6332..d8cb1adbc37bc2 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -616,6 +616,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdateHandlerAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdateHandlerAttribute.cs new file mode 100644 index 00000000000000..50eb4f9060d6c5 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdateHandlerAttribute.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace System.Reflection.Metadata +{ + /// Specifies a type that should receive notifications of metadata updates. + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class MetadataUpdateHandlerAttribute : Attribute + { + /// Initializes the attribute. + /// A type that handles metadata updates and that should be notified when any occur. + public MetadataUpdateHandlerAttribute([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type handlerType) => + HandlerType = handlerType; + + /// Gets the type that handles metadata updates and that should be notified when any occur. + public Type HandlerType { get; } + } +} diff --git a/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs b/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs index 8279cfa8b9c43e..8d08e9b35f62e7 100644 --- a/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs +++ b/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs @@ -12,6 +12,12 @@ public static partial class AssemblyExtensions public unsafe static bool TryGetRawMetadata(this System.Reflection.Assembly assembly, out byte* blob, out int length) { throw null; } public static void ApplyUpdate(Assembly assembly, ReadOnlySpan metadataDelta, ReadOnlySpan ilDelta, ReadOnlySpan pdbDelta) { throw null; } } + [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class MetadataUpdateHandlerAttribute : System.Attribute + { + public MetadataUpdateHandlerAttribute([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type handlerType) { } + public System.Type HandlerType { get { throw null; } } + } } namespace System.Runtime.Loader { diff --git a/src/libraries/System.Runtime.Loader/tests/MetadataUpdateHandlerAttributeTest.cs b/src/libraries/System.Runtime.Loader/tests/MetadataUpdateHandlerAttributeTest.cs new file mode 100644 index 00000000000000..59c61d7d8a65df --- /dev/null +++ b/src/libraries/System.Runtime.Loader/tests/MetadataUpdateHandlerAttributeTest.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Reflection.Metadata +{ + public class MetadataUpdateHandlerAttributeTest + { + [Fact] + public void Ctor_RoundtripType() + { + Type t = typeof(MetadataUpdateHandlerAttributeTest); + var a = new MetadataUpdateHandlerAttribute(t); + Assert.Same(t, a.HandlerType); + } + } +} diff --git a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj index 11c1b7577fff9d..1ab71b1f41abfe 100644 --- a/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj +++ b/src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csproj @@ -13,6 +13,7 @@ + diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj index e4b0b3580f5853..dea6068d8881a6 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj @@ -206,6 +206,7 @@ + diff --git a/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs new file mode 100644 index 00000000000000..d09b79964e307c --- /dev/null +++ b/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Reflection.Tests +{ + [SkipOnMono("Mono doesn't have a reflection cache")] + public class ReflectionCacheTests + { + [Fact] + public void GetMethod_MultipleCalls_SameObjects() + { + MethodInfo mi1 = typeof(ReflectionCacheTests).GetMethod(nameof(GetMethod_MultipleCalls_SameObjects)); + Assert.NotNull(mi1); + + MethodInfo mi2 = typeof(ReflectionCacheTests).GetMethod(nameof(GetMethod_MultipleCalls_SameObjects)); + Assert.NotNull(mi2); + + Assert.Same(mi1, mi2); + } + + [Fact] + public void GetMethod_MultipleCalls_ClearCache_DifferentObjects() + { + Type updateHandler = typeof(Type).Assembly.GetType("System.Reflection.Metadata.RuntimeTypeMetadataUpdateHandler", throwOnError: true, ignoreCase: false); + MethodInfo beforeUpdate = updateHandler.GetMethod("BeforeUpdate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, new[] { typeof(Type) }); + Assert.NotNull(beforeUpdate); + + MethodInfo mi1 = typeof(ReflectionCacheTests).GetMethod(nameof(GetMethod_MultipleCalls_ClearCache_DifferentObjects)); + Assert.NotNull(mi1); + Assert.Equal(nameof(GetMethod_MultipleCalls_ClearCache_DifferentObjects), mi1.Name); + + beforeUpdate.Invoke(null, new object[] { typeof(ReflectionCacheTests) }); + + MethodInfo mi2 = typeof(ReflectionCacheTests).GetMethod(nameof(GetMethod_MultipleCalls_ClearCache_DifferentObjects)); + Assert.NotNull(mi2); + Assert.Equal(nameof(GetMethod_MultipleCalls_ClearCache_DifferentObjects), mi2.Name); + + Assert.NotSame(mi1, mi2); + } + } +} From 693c22b93dfe8c51e72b32b37e4900fa9362e9e5 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 8 Apr 2021 21:13:34 -0400 Subject: [PATCH 2/3] Address PR feedback --- .../src/ILLink/ILLink.LinkAttributes.Shared.xml | 5 +++++ .../Reflection/Metadata/MetadataUpdateHandlerAttribute.cs | 1 + .../tests/System/Reflection/ReflectionCacheTests.cs | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.LinkAttributes.Shared.xml b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.LinkAttributes.Shared.xml index 8df05e609708b6..d910ad1eb64186 100644 --- a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.LinkAttributes.Shared.xml +++ b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.LinkAttributes.Shared.xml @@ -29,6 +29,11 @@ + + + + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdateHandlerAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdateHandlerAttribute.cs index 50eb4f9060d6c5..7af3be1644d8ac 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdateHandlerAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdateHandlerAttribute.cs @@ -15,6 +15,7 @@ public MetadataUpdateHandlerAttribute([DynamicallyAccessedMembers(DynamicallyAcc HandlerType = handlerType; /// Gets the type that handles metadata updates and that should be notified when any occur. + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] public Type HandlerType { get; } } } diff --git a/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs index d09b79964e307c..f295be7f73faf0 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/ReflectionCacheTests.cs @@ -5,7 +5,6 @@ namespace System.Reflection.Tests { - [SkipOnMono("Mono doesn't have a reflection cache")] public class ReflectionCacheTests { [Fact] @@ -20,6 +19,7 @@ public void GetMethod_MultipleCalls_SameObjects() Assert.Same(mi1, mi2); } + [ActiveIssue("https://github.com/dotnet/runtime/issues/50978", TestRuntimes.Mono)] [Fact] public void GetMethod_MultipleCalls_ClearCache_DifferentObjects() { From 4b5494ed3655cd1e9f91c8e27e70b608fe297098 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 9 Apr 2021 10:51:53 -0400 Subject: [PATCH 3/3] Update src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs Co-authored-by: Eric Erhardt --- src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs b/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs index 8d08e9b35f62e7..e42a0642b32ea9 100644 --- a/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs +++ b/src/libraries/System.Runtime.Loader/ref/System.Runtime.Loader.cs @@ -16,6 +16,7 @@ public static partial class AssemblyExtensions public sealed class MetadataUpdateHandlerAttribute : System.Attribute { public MetadataUpdateHandlerAttribute([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type handlerType) { } + [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] public System.Type HandlerType { get { throw null; } } } }