-
Notifications
You must be signed in to change notification settings - Fork 5k
Use AssemblyLoadContext-aware caches in TypeDescriptor to support unloading of assemblies cached by TypeDescriptor #114619
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
base: main
Are you sure you want to change the base?
Conversation
Tagging subscribers to this area: @dotnet/area-system-componentmodel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot reviewed 6 out of 9 changed files in this pull request and generated no comments.
Files not reviewed (3)
- src/libraries/System.ComponentModel.TypeConverter/System.ComponentModel.TypeConverter.sln: Language not supported
- src/libraries/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj: Language not supported
- src/libraries/System.ComponentModel.TypeConverter/tests/UnloadableTestTypes/UnloadableTestTypes.csproj: Language not supported
Comments suppressed due to low confidence (1)
src/libraries/System.ComponentModel.TypeConverter/tests/ReflectionCachesUpdateHandlerTests.cs:104
- Consider increasing the number of garbage collection attempts or using a more robust timeout mechanism to ensure that the assembly unloads reliably across different environments.
for (int i = 0; weakRef.IsAlive && i < 10; i++)
MetadataUpdateHandler is not meant to be used to prepare caches for AssemblyLoadContext unloading. |
The simplest way to avoid the overhead is to skip the caching completely for collectible types. We do that in number of places in this repo. |
thanks for the suggestion! |
If the performance regression is not acceptable, it is an option to have two caches - one for collectible types and one for non-collectible types. It avoids the performance overhead of weak handles for non-colllectible types. |
@jkotas to clarify - would you be ok with a solution that keeps existing cache for types in non-collectible ALCs and WeakHashtable for the types in collectible ALCs (for both TypeDescriptor and ReflectTypeDescriptionProvider cache levels), correct? I like this approach, as it gives correctness and performance at a relatively low cost of calling Type.IsCollectible 😄 |
@@ -2530,6 +2530,32 @@ public static void Refresh(Assembly assembly) | |||
} | |||
} | |||
|
|||
internal static void ClearCache(Type type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and the other ClearCache() below will need to lock on:
lock (s_commonSyncObject)
Adding items to s_defaultProviderInitialized
is already protected this way and so should removing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks @steveharter !
to clarify - are you ok with the approach in the PR or should I split caches into 2 - one for non-collectible types and one for collectible types (then we don't need ClearCache changes)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these already protected since they call TypeDescriptor.Refresh(...) methods?
they are protected in isolation - s_commonSyncObject
as I understand protects s_providerTable
as well as access to the cached providers
s_defaultProviderInitialized
is a ConcurrentDictionary
, so it does not need lock here
but enumeration of the s_defaultProviderInitialized
needs the lock since the content can be modified during enumeration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we are fine with the ClearCache
change I'll fix the locks usage, however if we prefer to split the caches to have a separate WeakHashtables s_providerTypeTable
and s_defaultProviderInitialized
for the collectible types, then we don't need current changes
I'm not sure on what issue this PR is current solving. Currently it depends on hot reload APIs to be called to clear caches, but that already works for hot reload purposes AFAIK. It doesn't solve the more general issue of unloading a collectible ALC. |
It would be fine with me. We have similar split in other places. |
@steveharter yes, I'm going to rework it to split the caches next week. Although there is one case which may need a cache clear approach - |
007504d
to
78c71fe
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR fixes an issue with ReflectTypeDescriptionProvider by ensuring that the cached type is completely removed during cache cleanup, allowing for proper unloading of AssemblyLoadContexts.
- Updated cache data structures to use ContextAwareConcurrentDictionary and ContextAwareHashtable.
- Added new tests to verify that unloadable types are correctly cleared from the cache.
- Refactored related helper classes (WeakHashtable, ContextAwareConcurrentDictionary, and ContextAwareHashtable) for improved cache management.
Reviewed Changes
Copilot reviewed 7 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
UnloadableTestTypes.cs | Added sample unloadable type and associated attribute for testing. |
TypeDescriptorTests.cs | Introduced new tests to validate cache clearance and proper unloading. |
WeakHashtable.cs | Modified weak reference handling for enumeration in caching. |
TypeDescriptor.cs | Updated cache implementation to use ContextAware data structures. |
ReflectTypeDescriptionProvider.cs | Refactored to use new ContextAwareConcurrentDictionary and improved threading comments. |
ContextAwareHashtable.cs | Added new implementation to handle collectible types in caching. |
ContextAwareConcurrentDictionary.cs | Introduced new concurrent dictionary to support collectible MemberInfo keys. |
Files not reviewed (4)
- src/libraries/System.ComponentModel.TypeConverter/System.ComponentModel.TypeConverter.sln: Language not supported
- src/libraries/System.ComponentModel.TypeConverter/src/System.ComponentModel.TypeConverter.csproj: Language not supported
- src/libraries/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj: Language not supported
- src/libraries/System.ComponentModel.TypeConverter/tests/UnloadableTestTypes/UnloadableTestTypes.csproj: Language not supported
src/libraries/System.ComponentModel.TypeConverter/tests/TypeDescriptorTests.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR fixes a caching issue in ReflectTypeDescriptionProvider by ensuring that collectible types are fully removed from static caches, thereby allowing AssemblyLoadContext unload. Key changes include:
- Transition from ConcurrentDictionary/Hashtable to context-aware collections (ContextAwareConcurrentDictionary and ContextAwareHashtable) for collectible types.
- Updates in TypeDescriptor and ReflectTypeDescriptionProvider to use the new collections.
- Addition of tests to confirm that unloadable types are not retained in the caches.
Reviewed Changes
Copilot reviewed 7 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
tests/UnloadableTestTypes/UnloadableTestTypes.cs | Added sample unloadable types for testing. |
tests/TypeDescriptorTests.cs | Introduced a new AssemblyLoadContext test to verify cache cleanup. |
src/System/ComponentModel/WeakHashtable.cs | Updated weak referencing via indexer override. |
src/System/ComponentModel/TypeDescriptor.cs | Switched to ContextAware collections and refined lock comments. |
src/System/ComponentModel/ReflectTypeDescriptionProvider.cs | Modified caching to use context-aware collections and improved comments. |
src/System/ComponentModel/ContextAwareHashtable.cs | New helper to conditionally use weak references for collectible keys. |
src/System/ComponentModel/ContextAwareConcurrentDictionary.cs | New concurrent dictionary variant for handling collectible keys. |
Files not reviewed (4)
- src/libraries/System.ComponentModel.TypeConverter/System.ComponentModel.TypeConverter.sln: Language not supported
- src/libraries/System.ComponentModel.TypeConverter/src/System.ComponentModel.TypeConverter.csproj: Language not supported
- src/libraries/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj: Language not supported
- src/libraries/System.ComponentModel.TypeConverter/tests/UnloadableTestTypes/UnloadableTestTypes.csproj: Language not supported
...m.ComponentModel.TypeConverter/src/System/ComponentModel/ContextAwareConcurrentDictionary.cs
Outdated
Show resolved
Hide resolved
@steveharter @jkotas I've updated the implementation to use That give us a good balance between functional correctness and performance as:
|
private static Hashtable? s_extendedPropertyCache; | ||
// The cache uses ContextAwareHashtable to ensure collectible types are stored via WeakReference | ||
// to allow for collectible AssemblyLoadContexts to be unloaded. | ||
private static ContextAwareHashtable<PropertyDescriptor[]>? s_propertyCache; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we want to keep a non-generic version to limit the amount of jit we need to do, I'm happy to make ContextAwareHashtable
non-generic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR fixes type caching issues to ensure unloadable assembly types are properly removed from static caches and introduces context-aware collections to hold weak references for collectible types.
- Updated tests to validate that unloadable types are cleared.
- Replaced standard collections with ContextAwareConcurrentDictionary and ContextAwareHashtable in caching logic.
- Added helper classes for weak referencing collectible types.
Reviewed Changes
Copilot reviewed 7 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
tests/UnloadableTestTypes/UnloadableTestTypes.cs | Added simple unloadable test types. |
tests/TypeDescriptorTests.cs | Added assembly load context and unload test to exercise cache clearing. |
src/System/ComponentModel/WeakHashtable.cs | Updated enumerator behavior to capture keys during iteration. |
src/System/ComponentModel/TypeDescriptor.cs | Migrated to context-aware collections for caching. |
src/System/ComponentModel/ReflectTypeDescriptionProvider.cs | Transitioned to context-aware caches and ensured proper removal criteria. |
src/System/ComponentModel/ContextAwareHashtable.cs | Introduced new hashtable for wrapping collectible keys in weak references. |
src/System/ComponentModel/ContextAwareConcurrentDictionary.cs | Added a new concurrent dictionary for mapping collectible keys using ConditionalWeakTable. |
Files not reviewed (4)
- src/libraries/System.ComponentModel.TypeConverter/System.ComponentModel.TypeConverter.sln: Language not supported
- src/libraries/System.ComponentModel.TypeConverter/src/System.ComponentModel.TypeConverter.csproj: Language not supported
- src/libraries/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj: Language not supported
- src/libraries/System.ComponentModel.TypeConverter/tests/UnloadableTestTypes/UnloadableTestTypes.csproj: Language not supported
...m.ComponentModel.TypeConverter/src/System/ComponentModel/ContextAwareConcurrentDictionary.cs
Outdated
Show resolved
Hide resolved
…oadability for the TypeDescriptor class
c5bed6c
to
96bc926
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR fixes an issue with ReflectTypeDescriptionProvider not completely clearing cached types and introduces context‐aware collections to support unloadable assemblies. Key changes include:
- Removal of strong references from caches via ContextAwareConcurrentDictionary and ContextAwareHashtable.
- Addition of new test types and unit tests to verify that unloadable assemblies are properly cleaned up.
- Updates to caching and enumeration logic in WeakHashtable and ReflectTypeDescriptionProvider.
Reviewed Changes
Copilot reviewed 7 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
src/libraries/System.ComponentModel.TypeConverter/tests/UnloadableTestTypes/UnloadableTestTypes.cs | Added simple test types for unloadable assembly scenarios. |
src/libraries/System.ComponentModel.TypeConverter/tests/TypeDescriptorTests.cs | Introduced tests ensuring that cached types from collectible assemblies are released. |
src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/WeakHashtable.cs | Updated to use indexer for setting weak references. |
src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/TypeDescriptor.cs | Replaced standard dictionaries/hashtables with context-aware collections. |
src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/ReflectTypeDescriptionProvider.cs | Updated to use context-aware caching and ensure complete cache cleanup. |
src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/ContextAwareHashtable.cs | Added new helper class for context-aware hashtable operations. |
src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/ContextAwareConcurrentDictionary.cs | Added new helper that uses ConditionalWeakTable for collectible keys alongside ConcurrentDictionary for others. |
Files not reviewed (4)
- src/libraries/System.ComponentModel.TypeConverter/System.ComponentModel.TypeConverter.sln: Language not supported
- src/libraries/System.ComponentModel.TypeConverter/src/System.ComponentModel.TypeConverter.csproj: Language not supported
- src/libraries/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj: Language not supported
- src/libraries/System.ComponentModel.TypeConverter/tests/UnloadableTestTypes/UnloadableTestTypes.csproj: Language not supported
} | ||
else | ||
{ | ||
_collectibleTable.AddOrUpdate(key, value!); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ConditionalWeakTable does not have an AddOrUpdate method in the standard API. Consider checking if an update is needed by removing the key first or using an alternative pattern that is supported.
_collectibleTable.AddOrUpdate(key, value!); | |
_collectibleTable.Remove(key); // Remove the key if it exists | |
_collectibleTable.Add(key, value!); // Add the new key-value pair |
Copilot uses AI. Check for mistakes.
Updated |
Fix cleanup of cached types in TypeDescriptor.
Motivation
MetadataUpdateHandler attribute is used to facilitate .NET HotReload and used in Unity to ensure Type caches are cleared on AssemblyLoadContext unloading.
However
ReflectTypeDescriptionProvider
currently does not fully clear types that were requested to be removed.While the cached properties for the type are cleared, the Type itself is kept in the _typeData ConcurrentDictionary.
This prevents
AssemblyLoadContext
from unloading.Changes
V1
The PR changes the
ReflectionCachesUpdateHandler
behavior to completely remove the requested types from the provider cache.V2
ContextAwareConcurrentDictionary
andContextAwareHashtable
helper classes that wrap key and values withWeakReference
for collectible types.TypeDescription
andReflectTypeDescriptionProvider
implementations to ensure there are no strong references from static caches to types from unloadable assemblies.The PR addresses
#114620#30656 issue.@simonferquel mentioned that the potential alternative could be to use
WeakHashtable
instead. However this would come with performance costs, so if cache clearing is fundamentally ok and doesn't break functionality I would prefer to keep it.Testing
TypeDescriptorTests.TypeDescriptor_DoesNotKeepUnloadableTypes
test to validate caching for collectible types.