mirror of https://github.com/dotnet/runtime
Merge edfac0ddcd
into 02596ba8d9
This commit is contained in:
commit
76f1a452b4
|
@ -1055,6 +1055,14 @@
|
|||
<Build Solution="Checked|x64" Project="false" />
|
||||
<Build Solution="Checked|x86" Project="false" />
|
||||
</Project>
|
||||
<Project Path="tests/UnloadableTestTypes/UnloadableTestTypes.csproj">
|
||||
<BuildType Solution="Checked|*" Project="Release" />
|
||||
<Build Solution="*|arm" Project="false" />
|
||||
<Build Solution="*|arm64" Project="false" />
|
||||
<Build Solution="Checked|Any CPU" Project="false" />
|
||||
<Build Solution="Checked|x64" Project="false" />
|
||||
<Build Solution="Checked|x86" Project="false" />
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/tools/" />
|
||||
<Folder Name="/tools/gen/">
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<Compile Include="System\ComponentModel\ByteConverter.cs" />
|
||||
<Compile Include="System\ComponentModel\CharConverter.cs" />
|
||||
<Compile Include="System\ComponentModel\CollectionConverter.cs" />
|
||||
<Compile Include="System\ComponentModel\CollectibleKeyConcurrentHashtable.cs" />
|
||||
<Compile Include="System\ComponentModel\DateOnlyConverter.cs" />
|
||||
<Compile Include="System\ComponentModel\DateTimeConverter.cs" />
|
||||
<Compile Include="System\ComponentModel\DateTimeOffsetConverter.cs" />
|
||||
|
@ -44,6 +45,7 @@
|
|||
<Compile Include="System\ComponentModel\UInt64Converter.cs" />
|
||||
<Compile Include="System\ComponentModel\UriTypeConverter.cs" />
|
||||
<Compile Include="System\ComponentModel\VersionConverter.cs" />
|
||||
<Compile Include="System\ComponentModel\CollectibleKeyHashtable.cs" />
|
||||
<Compile Include="System\Timers\ElapsedEventArgs.cs" />
|
||||
<Compile Include="System\Timers\ElapsedEventHandler.cs" />
|
||||
<Compile Include="System\Timers\Timer.cs">
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace System.ComponentModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Concurrent dictionary that maps MemberInfo object key to an object.
|
||||
/// Uses ConditionalWeakTable for the collectible keys (if MemberInfo.IsCollectible is true) and
|
||||
/// ConcurrentDictionary for non-collectible keys.
|
||||
/// </summary>
|
||||
internal sealed class CollectibleKeyConcurrentHashtable<TKey, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
|
||||
where TKey : MemberInfo
|
||||
where TValue : class?
|
||||
{
|
||||
private readonly ConcurrentDictionary<TKey, TValue> _defaultTable = new ConcurrentDictionary<TKey, TValue>();
|
||||
private readonly ConditionalWeakTable<TKey, object?> _collectibleTable = new ConditionalWeakTable<TKey, object?>();
|
||||
|
||||
public TValue? this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return TryGetValue(key, out TValue? value) ? value : default;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (!key.IsCollectible)
|
||||
{
|
||||
_defaultTable[key] = value!;
|
||||
}
|
||||
else
|
||||
{
|
||||
_collectibleTable.AddOrUpdate(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
return !key.IsCollectible ? _defaultTable.ContainsKey(key) : _collectibleTable.TryGetValue(key, out _);
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
|
||||
{
|
||||
if (!key.IsCollectible)
|
||||
return _defaultTable.TryGetValue(key, out value);
|
||||
|
||||
if (_collectibleTable.TryGetValue(key, out object? valueObj) && valueObj != null)
|
||||
{
|
||||
value = (TValue)valueObj;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryAdd(TKey key, TValue value)
|
||||
{
|
||||
return !key.IsCollectible
|
||||
? _defaultTable.TryAdd(key, value)
|
||||
: _collectibleTable.TryAdd(key, value);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_defaultTable.Clear();
|
||||
_collectibleTable.Clear();
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() =>
|
||||
new Enumerator(_defaultTable.GetEnumerator(), ((IEnumerable<KeyValuePair<TKey, object?>>)_collectibleTable).GetEnumerator());
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
private sealed class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
|
||||
{
|
||||
private readonly IEnumerator<KeyValuePair<TKey, TValue>> _defaultEnumerator;
|
||||
private readonly IEnumerator<KeyValuePair<TKey, object?>> _collectibleEnumerator;
|
||||
private bool _enumeratingCollectibleEnumerator;
|
||||
|
||||
public Enumerator(IEnumerator<KeyValuePair<TKey, TValue>> defaultEnumerator, IEnumerator<KeyValuePair<TKey, object?>> collectibleEnumerator)
|
||||
{
|
||||
_defaultEnumerator = defaultEnumerator;
|
||||
_collectibleEnumerator = collectibleEnumerator;
|
||||
_enumeratingCollectibleEnumerator = false;
|
||||
}
|
||||
|
||||
public KeyValuePair<TKey, TValue> Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_defaultEnumerator.Dispose();
|
||||
_collectibleEnumerator.Dispose();
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (!_enumeratingCollectibleEnumerator && _defaultEnumerator.MoveNext())
|
||||
{
|
||||
Current = _defaultEnumerator.Current;
|
||||
return true;
|
||||
}
|
||||
|
||||
_enumeratingCollectibleEnumerator = true;
|
||||
|
||||
while (_collectibleEnumerator.MoveNext())
|
||||
{
|
||||
if (_collectibleEnumerator.Current.Value is TValue value)
|
||||
{
|
||||
Current = new KeyValuePair<TKey, TValue>(_collectibleEnumerator.Current.Key, value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Current = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_defaultEnumerator.Reset();
|
||||
_collectibleEnumerator.Reset();
|
||||
_enumeratingCollectibleEnumerator = false;
|
||||
Current = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace System.ComponentModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Hashtable that maps a <see cref="MemberInfo"/> object key to an associated value.
|
||||
/// <para>
|
||||
/// For keys where <see cref="MemberInfo.IsCollectible"/> is <c>false</c>, a standard <see cref="Hashtable"/> is used.
|
||||
/// For keys where <see cref="MemberInfo.IsCollectible"/> is <c>true</c>, a <see cref="ConditionalWeakTable{TKey, TValue}"/> is used.
|
||||
/// This ensures that collectible <see cref="MemberInfo"/> instances (such as those from collectible assemblies) do not prevent their assemblies from being unloaded.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal sealed class CollectibleKeyHashtable
|
||||
{
|
||||
private readonly Hashtable _defaultTable = new Hashtable();
|
||||
private readonly ConditionalWeakTable<object, object?> _collectibleTable = new ConditionalWeakTable<object, object?>();
|
||||
|
||||
public object? this[MemberInfo key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return !key.IsCollectible ? _defaultTable[key] : (_collectibleTable.TryGetValue(key, out object? value) ? value : null);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (!key.IsCollectible)
|
||||
{
|
||||
_defaultTable[key] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
_collectibleTable.AddOrUpdate(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
@ -24,7 +23,7 @@ namespace System.ComponentModel
|
|||
internal sealed partial class ReflectTypeDescriptionProvider : TypeDescriptionProvider
|
||||
{
|
||||
// ReflectedTypeData contains all of the type information we have gathered for a given type.
|
||||
private readonly ConcurrentDictionary<Type, ReflectedTypeData> _typeData = new ConcurrentDictionary<Type, ReflectedTypeData>();
|
||||
private readonly CollectibleKeyConcurrentHashtable<Type, ReflectedTypeData> _typeData = new CollectibleKeyConcurrentHashtable<Type, ReflectedTypeData>();
|
||||
|
||||
// This is the signature we look for when creating types that are generic, but
|
||||
// want to know what type they are dealing with. Enums are a good example of this;
|
||||
|
@ -49,10 +48,10 @@ namespace System.ComponentModel
|
|||
// on Control, Component and object are also automatically filled
|
||||
// in. The keys to the property and event caches are types.
|
||||
// The keys to the attribute cache are either MemberInfos or types.
|
||||
private static Hashtable? s_propertyCache;
|
||||
private static Hashtable? s_eventCache;
|
||||
private static Hashtable? s_attributeCache;
|
||||
private static Hashtable? s_extendedPropertyCache;
|
||||
private static CollectibleKeyHashtable? s_propertyCache;
|
||||
private static CollectibleKeyHashtable? s_eventCache;
|
||||
private static CollectibleKeyHashtable? s_attributeCache;
|
||||
private static CollectibleKeyHashtable? s_extendedPropertyCache;
|
||||
|
||||
// These are keys we stuff into our object cache. We use this
|
||||
// cache data to store extender provider info for an object.
|
||||
|
@ -193,13 +192,13 @@ namespace System.ComponentModel
|
|||
Justification = "IntrinsicTypeConverters is marked with RequiresUnreferencedCode. It is the only place that should call this.")]
|
||||
private static NullableConverter CreateNullableConverter(Type type) => new NullableConverter(type);
|
||||
|
||||
private static Hashtable PropertyCache => LazyInitializer.EnsureInitialized(ref s_propertyCache, () => new Hashtable());
|
||||
private static CollectibleKeyHashtable PropertyCache => LazyInitializer.EnsureInitialized(ref s_propertyCache, () => new CollectibleKeyHashtable());
|
||||
|
||||
private static Hashtable EventCache => LazyInitializer.EnsureInitialized(ref s_eventCache, () => new Hashtable());
|
||||
private static CollectibleKeyHashtable EventCache => LazyInitializer.EnsureInitialized(ref s_eventCache, () => new CollectibleKeyHashtable());
|
||||
|
||||
private static Hashtable AttributeCache => LazyInitializer.EnsureInitialized(ref s_attributeCache, () => new Hashtable());
|
||||
private static CollectibleKeyHashtable AttributeCache => LazyInitializer.EnsureInitialized(ref s_attributeCache, () => new CollectibleKeyHashtable());
|
||||
|
||||
private static Hashtable ExtendedPropertyCache => LazyInitializer.EnsureInitialized(ref s_extendedPropertyCache, () => new Hashtable());
|
||||
private static CollectibleKeyHashtable ExtendedPropertyCache => LazyInitializer.EnsureInitialized(ref s_extendedPropertyCache, () => new CollectibleKeyHashtable());
|
||||
|
||||
/// <summary>Clear the global caches this maintains on top of reflection.</summary>
|
||||
internal static void ClearReflectionCaches()
|
||||
|
@ -1096,7 +1095,7 @@ namespace System.ComponentModel
|
|||
/// </summary>
|
||||
internal static Attribute[] ReflectGetAttributes(Type type)
|
||||
{
|
||||
Hashtable attributeCache = AttributeCache;
|
||||
CollectibleKeyHashtable attributeCache = AttributeCache;
|
||||
Attribute[]? attrs = (Attribute[]?)attributeCache[type];
|
||||
if (attrs != null)
|
||||
{
|
||||
|
@ -1124,7 +1123,7 @@ namespace System.ComponentModel
|
|||
/// </summary>
|
||||
internal static Attribute[] ReflectGetAttributes(MemberInfo member)
|
||||
{
|
||||
Hashtable attributeCache = AttributeCache;
|
||||
CollectibleKeyHashtable attributeCache = AttributeCache;
|
||||
Attribute[]? attrs = (Attribute[]?)attributeCache[member];
|
||||
if (attrs != null)
|
||||
{
|
||||
|
@ -1152,7 +1151,7 @@ namespace System.ComponentModel
|
|||
/// </summary>
|
||||
private static EventDescriptor[] ReflectGetEvents(Type type)
|
||||
{
|
||||
Hashtable eventCache = EventCache;
|
||||
CollectibleKeyHashtable eventCache = EventCache;
|
||||
EventDescriptor[]? events = (EventDescriptor[]?)eventCache[type];
|
||||
if (events != null)
|
||||
{
|
||||
|
@ -1252,7 +1251,7 @@ namespace System.ComponentModel
|
|||
// property store.
|
||||
//
|
||||
Type providerType = provider.GetType();
|
||||
Hashtable extendedPropertyCache = ExtendedPropertyCache;
|
||||
CollectibleKeyHashtable extendedPropertyCache = ExtendedPropertyCache;
|
||||
ReflectPropertyDescriptor[]? extendedProperties = (ReflectPropertyDescriptor[]?)extendedPropertyCache[providerType];
|
||||
if (extendedProperties == null)
|
||||
{
|
||||
|
@ -1337,7 +1336,7 @@ namespace System.ComponentModel
|
|||
|
||||
private static PropertyDescriptor[] ReflectGetPropertiesImpl(Type type)
|
||||
{
|
||||
Hashtable propertyCache = PropertyCache;
|
||||
CollectibleKeyHashtable propertyCache = PropertyCache;
|
||||
PropertyDescriptor[]? properties = (PropertyDescriptor[]?)propertyCache[type];
|
||||
if (properties != null)
|
||||
{
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel.Design;
|
||||
|
@ -67,12 +66,12 @@ namespace System.ComponentModel
|
|||
internal static readonly object s_commonSyncObject = new object();
|
||||
|
||||
// A direct mapping from type to provider.
|
||||
private static readonly ConcurrentDictionary<Type, TypeDescriptionNode> s_providerTypeTable = new ConcurrentDictionary<Type, TypeDescriptionNode>();
|
||||
private static readonly CollectibleKeyConcurrentHashtable<Type, TypeDescriptionNode> s_providerTypeTable = new CollectibleKeyConcurrentHashtable<Type, TypeDescriptionNode>();
|
||||
|
||||
// Tracks DefaultTypeDescriptionProviderAttributes.
|
||||
// A value of `null` indicates initialization is in progress.
|
||||
// A value of s_initializedDefaultProvider indicates the provider is initialized.
|
||||
private static readonly ConcurrentDictionary<Type, object?> s_defaultProviderInitialized = new ConcurrentDictionary<Type, object?>();
|
||||
private static readonly CollectibleKeyConcurrentHashtable<Type, object?> s_defaultProviderInitialized = new CollectibleKeyConcurrentHashtable<Type, object?>();
|
||||
|
||||
private static readonly object s_initializedDefaultProvider = new object();
|
||||
|
||||
|
@ -293,7 +292,7 @@ namespace System.ComponentModel
|
|||
refreshNeeded = s_providerTable.ContainsKey(instance);
|
||||
TypeDescriptionNode node = NodeFor(instance, true);
|
||||
var head = new TypeDescriptionNode(provider) { Next = node };
|
||||
s_providerTable.SetWeak(instance, head);
|
||||
s_providerTable[instance] = head;
|
||||
s_providerTypeTable.Clear();
|
||||
}
|
||||
|
||||
|
@ -433,7 +432,7 @@ namespace System.ComponentModel
|
|||
if (associations == null)
|
||||
{
|
||||
associations = new ArrayList(4);
|
||||
associationTable.SetWeak(primary, associations);
|
||||
associationTable[primary] = associations;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -624,7 +623,7 @@ namespace System.ComponentModel
|
|||
if (!type.IsInstanceOfType(primary))
|
||||
{
|
||||
// Check our association table for a match.
|
||||
Hashtable assocTable = AssociationTable;
|
||||
WeakHashtable assocTable = AssociationTable;
|
||||
IList? associations = (IList?)assocTable?[primary];
|
||||
if (associations != null)
|
||||
{
|
||||
|
@ -2334,16 +2333,12 @@ namespace System.ComponentModel
|
|||
// ReflectTypeDescritionProvider is only bound to object, but we
|
||||
// need go to through the entire table to try to find custom
|
||||
// providers. If we find one, will clear our cache.
|
||||
// Manual use of IDictionaryEnumerator instead of foreach to avoid
|
||||
// DictionaryEntry box allocations.
|
||||
IDictionaryEnumerator e = s_providerTable.GetEnumerator();
|
||||
while (e.MoveNext())
|
||||
foreach (KeyValuePair<object, object?> kvp in s_providerTable)
|
||||
{
|
||||
DictionaryEntry de = e.Entry;
|
||||
Type? nodeType = de.Key as Type;
|
||||
Type? nodeType = kvp.Key as Type;
|
||||
if (nodeType != null && type.IsAssignableFrom(nodeType) || nodeType == typeof(object))
|
||||
{
|
||||
TypeDescriptionNode? node = (TypeDescriptionNode?)de.Value;
|
||||
TypeDescriptionNode? node = (TypeDescriptionNode?)kvp.Value;
|
||||
while (node != null && !(node.Provider is ReflectTypeDescriptionProvider))
|
||||
{
|
||||
found = true;
|
||||
|
@ -2418,16 +2413,12 @@ namespace System.ComponentModel
|
|||
// ReflectTypeDescritionProvider is only bound to object, but we
|
||||
// need go to through the entire table to try to find custom
|
||||
// providers. If we find one, will clear our cache.
|
||||
// Manual use of IDictionaryEnumerator instead of foreach to avoid
|
||||
// DictionaryEntry box allocations.
|
||||
IDictionaryEnumerator e = s_providerTable.GetEnumerator();
|
||||
while (e.MoveNext())
|
||||
foreach (KeyValuePair<object, object?> kvp in s_providerTable)
|
||||
{
|
||||
DictionaryEntry de = e.Entry;
|
||||
Type? nodeType = de.Key as Type;
|
||||
Type? nodeType = kvp.Key as Type;
|
||||
if (nodeType != null && type.IsAssignableFrom(nodeType) || nodeType == typeof(object))
|
||||
{
|
||||
TypeDescriptionNode? node = (TypeDescriptionNode?)de.Value;
|
||||
TypeDescriptionNode? node = (TypeDescriptionNode?)kvp.Value;
|
||||
while (node != null && !(node.Provider is ReflectTypeDescriptionProvider))
|
||||
{
|
||||
found = true;
|
||||
|
@ -2480,15 +2471,12 @@ namespace System.ComponentModel
|
|||
|
||||
lock (s_commonSyncObject)
|
||||
{
|
||||
// Manual use of IDictionaryEnumerator instead of foreach to avoid DictionaryEntry box allocations.
|
||||
IDictionaryEnumerator e = s_providerTable.GetEnumerator();
|
||||
while (e.MoveNext())
|
||||
foreach (KeyValuePair<object, object?> kvp in s_providerTable)
|
||||
{
|
||||
DictionaryEntry de = e.Entry;
|
||||
Type? nodeType = de.Key as Type;
|
||||
Type? nodeType = kvp.Key as Type;
|
||||
if (nodeType != null && nodeType.Module.Equals(module) || nodeType == typeof(object))
|
||||
{
|
||||
TypeDescriptionNode? node = (TypeDescriptionNode?)de.Value;
|
||||
TypeDescriptionNode? node = (TypeDescriptionNode?)kvp.Value;
|
||||
while (node != null && !(node.Provider is ReflectTypeDescriptionProvider))
|
||||
{
|
||||
refreshedTypes ??= new Hashtable();
|
||||
|
@ -2630,7 +2618,7 @@ namespace System.ComponentModel
|
|||
ArgumentNullException.ThrowIfNull(primary);
|
||||
ArgumentNullException.ThrowIfNull(secondary);
|
||||
|
||||
Hashtable assocTable = AssociationTable;
|
||||
WeakHashtable assocTable = AssociationTable;
|
||||
IList? associations = (IList?)assocTable?[primary];
|
||||
if (associations != null)
|
||||
{
|
||||
|
|
|
@ -3,170 +3,31 @@
|
|||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace System.ComponentModel
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a hashtable that stores object keys as weak references.
|
||||
/// It monitors memory usage and will periodically scavenge the
|
||||
/// hash table to clean out dead references.
|
||||
/// Provides a hashtable-like collection that stores keys and values using weak references.
|
||||
/// This class is a thin wrapper around <see cref="ConditionalWeakTable{TKey, TValue}"/>
|
||||
/// and is especially useful for associating data with objects from unloadable assemblies.
|
||||
/// </summary>
|
||||
internal sealed class WeakHashtable : Hashtable
|
||||
internal sealed class WeakHashtable : IEnumerable<KeyValuePair<object, object?>>
|
||||
{
|
||||
private static readonly IEqualityComparer s_comparer = new WeakKeyComparer();
|
||||
private readonly ConditionalWeakTable<object, object?> _hashtable = new ConditionalWeakTable<object, object?>();
|
||||
|
||||
private long _lastGlobalMem;
|
||||
private int _lastHashCount;
|
||||
|
||||
internal WeakHashtable() : base(s_comparer)
|
||||
public object? this[object key]
|
||||
{
|
||||
get => _hashtable.TryGetValue(key, out object? value) ? value : null;
|
||||
set => _hashtable.AddOrUpdate(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override of Item that wraps a weak reference around the
|
||||
/// key and performs a scavenge.
|
||||
/// </summary>
|
||||
public void SetWeak(object key, object value)
|
||||
{
|
||||
ScavengeKeys();
|
||||
this[new EqualityWeakReference(key)] = value;
|
||||
}
|
||||
public bool ContainsKey(object key) => _hashtable.TryGetValue(key, out object? _);
|
||||
|
||||
/// <summary>
|
||||
/// This method checks to see if it is necessary to
|
||||
/// scavenge keys, and if it is it performs a scan
|
||||
/// of all keys to see which ones are no longer valid.
|
||||
/// To determine if we need to scavenge keys we need to
|
||||
/// try to track the current GC memory. Our rule of
|
||||
/// thumb is that if GC memory is decreasing and our
|
||||
/// key count is constant we need to scavenge. We
|
||||
/// will need to see if this is too often for extreme
|
||||
/// use cases like the CompactFramework (they add
|
||||
/// custom type data for every object at design time).
|
||||
/// </summary>
|
||||
private void ScavengeKeys()
|
||||
{
|
||||
int hashCount = Count;
|
||||
public void Remove(object key) => _hashtable.Remove(key);
|
||||
|
||||
if (hashCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
public IEnumerator<KeyValuePair<object, object?>> GetEnumerator() => ((IEnumerable<KeyValuePair<object, object?>>)_hashtable).GetEnumerator();
|
||||
|
||||
if (_lastHashCount == 0)
|
||||
{
|
||||
_lastHashCount = hashCount;
|
||||
return;
|
||||
}
|
||||
|
||||
long globalMem = GC.GetTotalMemory(false);
|
||||
|
||||
if (_lastGlobalMem == 0)
|
||||
{
|
||||
_lastGlobalMem = globalMem;
|
||||
return;
|
||||
}
|
||||
|
||||
float memDelta = (globalMem - _lastGlobalMem) / (float)_lastGlobalMem;
|
||||
float hashDelta = (hashCount - _lastHashCount) / (float)_lastHashCount;
|
||||
|
||||
if (memDelta < 0 && hashDelta >= 0)
|
||||
{
|
||||
// Perform a scavenge through our keys, looking
|
||||
// for dead references.
|
||||
List<object>? cleanupList = null;
|
||||
foreach (object o in Keys)
|
||||
{
|
||||
if (o is WeakReference wr && !wr.IsAlive)
|
||||
{
|
||||
cleanupList ??= new List<object>();
|
||||
cleanupList.Add(wr);
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanupList != null)
|
||||
{
|
||||
foreach (object o in cleanupList)
|
||||
{
|
||||
Remove(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_lastGlobalMem = globalMem;
|
||||
_lastHashCount = hashCount;
|
||||
}
|
||||
|
||||
private sealed class WeakKeyComparer : IEqualityComparer
|
||||
{
|
||||
bool IEqualityComparer.Equals(object? x, object? y)
|
||||
{
|
||||
if (x == null)
|
||||
{
|
||||
return y == null;
|
||||
}
|
||||
if (y != null && x.GetHashCode() == y.GetHashCode())
|
||||
{
|
||||
if (x is WeakReference wX)
|
||||
{
|
||||
if (!wX.IsAlive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
x = wX.Target;
|
||||
}
|
||||
|
||||
if (y is WeakReference wY)
|
||||
{
|
||||
if (!wY.IsAlive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
y = wY.Target;
|
||||
}
|
||||
|
||||
return object.ReferenceEquals(x, y);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int IEqualityComparer.GetHashCode(object obj) => obj.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A subclass of WeakReference that overrides GetHashCode and
|
||||
/// Equals so that the weak reference returns the same equality
|
||||
/// semantics as the object it wraps. This will always return
|
||||
/// the object's hash code and will return True for a Equals
|
||||
/// comparison of the object it is wrapping. If the object
|
||||
/// it is wrapping has finalized, Equals always returns false.
|
||||
/// </summary>
|
||||
private sealed class EqualityWeakReference : WeakReference
|
||||
{
|
||||
private readonly int _hashCode;
|
||||
|
||||
internal EqualityWeakReference(object o) : base(o)
|
||||
{
|
||||
_hashCode = o.GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(object? o)
|
||||
{
|
||||
if (o?.GetHashCode() != _hashCode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (o == this || (IsAlive && ReferenceEquals(o, Target)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => _hashCode;
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<KeyValuePair<object, object?>>)_hashtable).GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -182,6 +182,9 @@
|
|||
<LastGenOutput>TestResx.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="UnloadableTestTypes\UnloadableTestTypes.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<TrimmerRootDescriptor Include="$(MSBuildThisFileDirectory)ILLink.Descriptors.xml" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -5,7 +5,11 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Loader;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.DotNet.RemoteExecutor;
|
||||
|
@ -1523,5 +1527,151 @@ namespace System.ComponentModel.Tests
|
|||
public class MyInheritedClassWithCustomTypeDescriptionProviderConverter : TypeConverter
|
||||
{
|
||||
}
|
||||
|
||||
private class TestAssemblyLoadContext : AssemblyLoadContext
|
||||
{
|
||||
private AssemblyDependencyResolver _resolver;
|
||||
|
||||
public TestAssemblyLoadContext(string name, bool isCollectible, string mainAssemblyToLoadPath = null) : base(name, isCollectible)
|
||||
{
|
||||
if (!PlatformDetection.IsBrowser)
|
||||
_resolver = new AssemblyDependencyResolver(mainAssemblyToLoadPath ?? Assembly.GetExecutingAssembly().Location);
|
||||
}
|
||||
|
||||
protected override Assembly Load(AssemblyName name)
|
||||
{
|
||||
if (PlatformDetection.IsBrowser)
|
||||
{
|
||||
return base.Load(name);
|
||||
}
|
||||
|
||||
string assemblyPath = _resolver.ResolveAssemblyToPath(name);
|
||||
if (assemblyPath != null)
|
||||
{
|
||||
return LoadFromAssemblyPath(assemblyPath);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// This method must be not inlined to ensure that the ALC is not captured on the caller's stack.
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
||||
private static void ExecuteAndUnload(string assemblyfile, Action<Assembly> assemblyAction, out WeakReference alcWeakRef)
|
||||
{
|
||||
var fullPath = Path.GetFullPath(assemblyfile);
|
||||
var alc = new TestAssemblyLoadContext("TypeDescriptorTests", true, fullPath);
|
||||
alcWeakRef = new WeakReference(alc);
|
||||
|
||||
// Load assembly by path. By name, and it gets loaded in the default ALC.
|
||||
var assembly = alc.LoadFromAssemblyPath(fullPath);
|
||||
Assert.NotEqual(AssemblyLoadContext.GetLoadContext(assembly), AssemblyLoadContext.Default);
|
||||
|
||||
using (AssemblyLoadContext.ContextualReflectionScope scope = alc.EnterContextualReflection())
|
||||
{
|
||||
// Perform action on the assembly from ALC inside the reflection scope
|
||||
assemblyAction(assembly);
|
||||
}
|
||||
|
||||
// Unload the ALC
|
||||
alc.Unload();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
// Lack of AssemblyDependencyResolver results in assemblies that are not loaded by path to get
|
||||
// loaded in the default ALC, which causes problems for this test.
|
||||
[SkipOnPlatform(TestPlatforms.Browser, "AssemblyDependencyResolver not supported in wasm")]
|
||||
[ActiveIssue("34072", TestRuntimes.Mono)]
|
||||
public static void TypeDescriptor_WithDefaultProvider_UnloadsUnloadableTypes()
|
||||
{
|
||||
ExecuteAndUnload("UnloadableTestTypes.dll",
|
||||
static (assembly) =>
|
||||
{
|
||||
// Ensure the type loaded in the intended non-Default ALC
|
||||
Type? collectibleType = assembly.GetType("UnloadableTestTypes.SimpleType");
|
||||
Assert.NotNull(collectibleType);
|
||||
Type? collectibleAttributeType = assembly.GetType("UnloadableTestTypes.SimpleTypeAttribute");
|
||||
Assert.NotNull(collectibleAttributeType);
|
||||
Attribute? collectibleAttribute = collectibleType.GetCustomAttribute(collectibleAttributeType);
|
||||
Assert.NotNull(collectibleAttribute);
|
||||
Type? collectibleTypeDescriptionProviderType = assembly.GetType("UnloadableTestTypes.SimpleTypeDescriptionProvider");
|
||||
Assert.NotNull(collectibleTypeDescriptionProviderType);
|
||||
|
||||
// Cache the type's cachable entities
|
||||
AttributeCollection attributes = TypeDescriptor.GetAttributes(collectibleType);
|
||||
Assert.True(attributes.Contains(collectibleAttribute));
|
||||
|
||||
EventDescriptorCollection events = TypeDescriptor.GetEvents(collectibleType);
|
||||
Assert.Equal(1, events.Count);
|
||||
|
||||
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(collectibleType);
|
||||
Assert.Equal(2, properties.Count);
|
||||
},
|
||||
out var weakRef);
|
||||
|
||||
// Force garbage collection to ensure the ALC is unloaded and the types are collected.
|
||||
for (int i = 0; weakRef.IsAlive && i < 10; i++)
|
||||
{
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
}
|
||||
|
||||
// Assert that the weak reference to the ALC is no longer alive,
|
||||
// indicating that the unloaded AssemblyLoadContext has been at least collected.
|
||||
Assert.True(!weakRef.IsAlive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
// Lack of AssemblyDependencyResolver results in assemblies that are not loaded by path to get
|
||||
// loaded in the default ALC, which causes problems for this test.
|
||||
[SkipOnPlatform(TestPlatforms.Browser, "AssemblyDependencyResolver not supported in wasm")]
|
||||
[ActiveIssue("34072", TestRuntimes.Mono)]
|
||||
public static void TypeDescriptor_WithCustomProvider_UnloadsUnloadableTypes()
|
||||
{
|
||||
ExecuteAndUnload("UnloadableTestTypes.dll",
|
||||
static (assembly) =>
|
||||
{
|
||||
// Ensure the type loaded in the intended non-Default ALC
|
||||
Type? collectibleType = assembly.GetType("UnloadableTestTypes.SimpleType");
|
||||
Assert.NotNull(collectibleType);
|
||||
Type? collectibleAttributeType = assembly.GetType("UnloadableTestTypes.SimpleTypeAttribute");
|
||||
Assert.NotNull(collectibleAttributeType);
|
||||
Attribute? collectibleAttribute = collectibleType.GetCustomAttribute(collectibleAttributeType);
|
||||
Assert.NotNull(collectibleAttribute);
|
||||
Type? collectibleTypeDescriptionProviderType = assembly.GetType("UnloadableTestTypes.SimpleTypeDescriptionProvider");
|
||||
Assert.NotNull(collectibleTypeDescriptionProviderType);
|
||||
|
||||
// Add provider to ensure it is registered and stored in the TypeDescriptor cache
|
||||
TypeDescriptionProvider collectibleProvider = (TypeDescriptionProvider)Activator.CreateInstance(collectibleTypeDescriptionProviderType);
|
||||
TypeDescriptor.AddProvider(collectibleProvider, collectibleType);
|
||||
|
||||
// Test that the provider is the expected collectible one
|
||||
AttributeCollection attributes = TypeDescriptor.GetAttributes(collectibleType);
|
||||
Assert.Empty(attributes);
|
||||
|
||||
EventDescriptorCollection events = TypeDescriptor.GetEvents(collectibleType);
|
||||
Assert.Empty(events);
|
||||
|
||||
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(collectibleType);
|
||||
Assert.Empty(properties);
|
||||
|
||||
TypeDescriptionProvider provider = TypeDescriptor.GetProvider(collectibleType);
|
||||
Assert.NotNull(provider);
|
||||
Assert.True(provider.IsSupportedType(collectibleType));
|
||||
Assert.False(provider.IsSupportedType(typeof(int)));
|
||||
},
|
||||
out var weakRef);
|
||||
|
||||
// Force garbage collection to ensure the ALC is unloaded and the types are collected.
|
||||
for (int i = 0; weakRef.IsAlive && i < 10; i++)
|
||||
{
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
}
|
||||
|
||||
// Assert that the weak reference to the ALC is no longer alive,
|
||||
// indicating that the unloaded AssemblyLoadContext has been at least collected.
|
||||
Assert.True(!weakRef.IsAlive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace UnloadableTestTypes
|
||||
{
|
||||
[SimpleType]
|
||||
public class SimpleType
|
||||
{
|
||||
public string P1 { get; set; }
|
||||
public int P2 { get; set; }
|
||||
public event Action ActionEvent;
|
||||
public void OnActionEvent()
|
||||
{
|
||||
ActionEvent?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.All)]
|
||||
public sealed class SimpleTypeAttribute : Attribute { }
|
||||
|
||||
public sealed class SimpleTypeDescriptionProvider : TypeDescriptionProvider
|
||||
{
|
||||
public override bool IsSupportedType(Type type) => type.AssemblyQualifiedName == typeof(SimpleType).AssemblyQualifiedName;
|
||||
|
||||
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) => new SimpleTypeDescriptor();
|
||||
|
||||
public sealed class SimpleTypeDescriptor : CustomTypeDescriptor { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="UnloadableTestTypes.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Loading…
Reference in New Issue