Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MetadataUpdateHandlerAttribute #50954

Merged
merged 3 commits into from
Apr 9, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeModule.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeParameterInfo.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimePropertyInfo.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Metadata\RuntimeTypeMetadataUpdateHandler.cs" />
<Compile Include="$(BclSourcesRoot)\System\Resources\ManifestBasedResourceGroveler.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\CrossLoaderAllocatorHashHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\DependentHandle.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>Metadata update handler used to clear a Type's reflection cache in response to a metadata update notification.</summary>
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?
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be enough to just set the GCHandle target to null. The GCHandle is weakhandle and so the code should be generally prepared to handle the null case.

#51070

{
// 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
<type fullname="System.Diagnostics.DebuggerVisualizerAttribute">
<attribute internal="RemoveAttributeInstances" />
</type>

<!-- Hot reload attributes-->
<type fullname="System.Reflection.Metadata.MetadataUpdateHandlerAttribute">
<attribute internal="RemoveAttributeInstances" />
</type>
</assembly>

<assembly fullname="System.Private.CoreLib" feature="System.Diagnostics.Tracing.EventSource.IsSupported" featurevalue="false">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Reflection\TypeDelegator.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Reflection\TypeFilter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Reflection\TypeInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Reflection\Metadata\MetadataUpdateHandlerAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\ResolveEventArgs.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\ResolveEventHandler.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Resources\FastResourceComparer.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>Specifies a type that should receive notifications of metadata updates.</summary>
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class MetadataUpdateHandlerAttribute : Attribute
{
/// <summary>Initializes the attribute.</summary>
/// <param name="handlerType">A type that handles metadata updates and that should be notified when any occur.</param>
public MetadataUpdateHandlerAttribute([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type handlerType) =>
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
HandlerType = handlerType;

/// <summary>Gets the type that handles metadata updates and that should be notified when any occur.</summary>
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public Type HandlerType { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte> metadataDelta, ReadOnlySpan<byte> ilDelta, ReadOnlySpan<byte> 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; } }
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
}
}
namespace System.Runtime.Loader
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

mikem8361 marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<Compile Include="CollectibleAssemblyLoadContextTest.cs" />
<Compile Include="ContextualReflection.cs" />
<Compile Include="CustomTPALoadContext.cs" />
<Compile Include="MetadataUpdateHandlerAttributeTest.cs" />
<Compile Include="ResourceAssemblyLoadContext.cs" />
<Compile Include="SatelliteAssemblies.cs" />
<Compile Include="LoaderLinkTest.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@
<Compile Include="System\Reflection\ObfuscateAssemblyAttributeTests.cs" />
<Compile Include="System\Reflection\ObfuscationAttributeTests.cs" />
<Compile Include="System\Reflection\PointerTests.cs" />
<Compile Include="System\Reflection\ReflectionCacheTests.cs" />
<Compile Include="System\Reflection\ReflectionContextTests.cs" />
<Compile Include="System\Reflection\ReflectionTypeLoadExceptionTests.cs" />
<Compile Include="System\Reflection\StrongNameKeyPairTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
{
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()
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
{
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);
}
}
}