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/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.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..7af3be1644d8ac --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdateHandlerAttribute.cs @@ -0,0 +1,21 @@ +// 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. + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + 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..e42a0642b32ea9 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,13 @@ 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) { } + [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] + 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..f295be7f73faf0 --- /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 +{ + 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); + } + + [ActiveIssue("https://github.com/dotnet/runtime/issues/50978", TestRuntimes.Mono)] + [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); + } + } +}