Thanks to visit codestin.com
Credit goes to github.com

Skip to content

TypeDescriptor caching blocks unloading of code which uses it from unloadable load context #30656

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

Open
vitek-karas opened this issue Aug 22, 2019 · 17 comments
Labels
area-System.ComponentModel design-discussion Ongoing discussion about design without consensus feature-request
Milestone

Comments

@vitek-karas
Copy link
Member

The implementation of TypeDescription and TypeConverter uses internal caches which hold strong references to the Type object. These remain even after the descriptors and converters are released by the user code.

If these types come from collectible assemblies (either dynamic types, or custom unloadable AssemblyLoadContext), this prevents unloading of such assembly.

This is exaggerated by the fact that TypeConverter is used by Newtonsoft.Json during serilization or deserialization of JSON. This is VERY common in user code. This effectively means that any app which uses plugins and wants to be able to unload the plugins successfully would have to prevent all such plugins from using Newtonsoft.Json serialization on objects from the plugin.

This is basically a feature ask to modify all the internal caches in TypeDescriptor/TypeConverter implementation to be based on weak references, so that it's possible to collect the types.

Customer issue which ran into this, with detailed analysis of what happens: https://github.com/dotnet/coreclr/issues/26271

It was possible to workaround in this case by loading the ComponentModel assemblies many times into separate ALCs, but that comes with large performance penalty and is not an obvious solution to the problem.

@vitek-karas
Copy link
Member Author

/cc @janvorli

@Wraith2
Copy link
Contributor

Wraith2 commented Aug 22, 2019

Would it make sense to make the caches ALC specific so that converters caches in each context don't leak and wait for collection to occur? [edit] I've just realised that they've internal to a third party assembly so that might not be the best solution but it might still be possible.

@vitek-karas
Copy link
Member Author

That could also work - not sure which solution is best for perf...
Currently there's no framework-wide mechanism to attach something to ALC, so it would have to be a custom solution (basically a weak hash table of all ALCs). It's kind of interesting because the TypeDescriptor.s_providerTable is already a WeakHashtable and couple of others are as well, but not all. I don't know the history of this code, why it was done this way.

@tarekgh
Copy link
Member

tarekgh commented Aug 23, 2019

@vitek-karas I assume this issue is not meeting the 3.0 bar so I marked it as for 5.0 release. Let me know if this is not the case.

@vitek-karas
Copy link
Member Author

I agree it does not meet 3.0, but I think we should at least consider it for 3.1 - it blocks lot of unloadability scenarios - with no good workaround.

@Wraith2
Copy link
Contributor

Wraith2 commented Aug 23, 2019

Just having a look through the code this change might be as simple as changing some Hashtable to WeakHashtable but it's going to have some performance degradations under load. There are also a number of places where WeakReference.IsAlive is used which I know is now suggested against so could be changed at the same time.

@maryamariyan
Copy link
Member

Talked offline with @vitek-karas and here is a possible action plan forward for this issue:

  • Possibly a simple fix is to replace one of the strong references with a weak reference. But we also want to check for perf regressions and another concern is for cases when GC after the fix clears the cache.

  • Also note, a sample repro for this issue is in description of https://github.com/dotnet/coreclr/issues/26271

@msftgits msftgits transferred this issue from dotnet/corefx Feb 1, 2020
@msftgits msftgits added this to the 5.0 milestone Feb 1, 2020
@maryamariyan maryamariyan added the untriaged New issue has not been triaged by the area owner label Feb 23, 2020
@ericstj ericstj added bug and removed untriaged New issue has not been triaged by the area owner labels Jul 1, 2020
@ericstj
Copy link
Member

ericstj commented Aug 26, 2020

Not seeing this meet the bar for 5.0 at this point either. We haven't seen a ton of interest in this outside of this report. The risk here is substantial as this code has been around for decades now and is a critical piece of windows App models like Winforms and WPF. We'd need to be very careful when touching this not to change behavior nor impact perf substantially. We're not prepared to take that amount of risk at this point in the product.

@ericstj ericstj modified the milestones: 5.0.0, 6.0.0 Aug 26, 2020
@raffaeler
Copy link

@ericstj Correlating the issues between LoadContexts and the TypeDescriptor is definitely too hard to have some feedback after such a short amount of time.
Also, many companies are just planning the migration of old apps to .NET Core and didn't happen yet.
I suspect this issue can be a blocker for those who will try to migrate and will immediately surrender to the fact that LoadContext «are not working» just because diagnosing the unloading is super-hard and there are 'bugs' like this issue.

Also, considering that .NET 5 is not a LTS, it is probably the best time to make the changes required from this issue. Any later changes would bring just more risks, even if someone had a ton of patience in understanding that the failure reason is the TypeDescriptor.

HTH

@ericstj
Copy link
Member

ericstj commented Aug 27, 2020

We're at the RC stage in 5.0., unfortunately it's too late to jam in a change like this. We still have a year for 6.0, we can put a change in early in 6.0 and let it bake to ensure it's solid before release.

Did anyone identify the specific tables impacted? Some tables cannot be made completely weak as they back static API that needs to behave predictably. Others can be made weak easily like s_defaultProviders which only contains Types and could likely be switched to use the string-fully-qualified-type-name. Also the WeakHashTable used here is only weak on the key, value is still rooted AFAICT. The values are scavenged only when a new key is added, which is less likely to happen at steady-state.

@raffaeler
Copy link

@ericstj I am not aware of other tables.

Is it also too late to expose a "free" like method to avoid nasty reflection workarounds?

@eerhardt
Copy link
Member

Moving to Future as this issue has been around and is not critical to fix in 7.0.

@eerhardt eerhardt modified the milestones: 7.0.0, Future Jul 13, 2022
@steveharter steveharter self-assigned this Apr 1, 2024
@vitek-karas vitek-karas changed the title TypeDescriptor caching blocks unloading of code using Newtonsoft.Json serialization TypeDescriptor caching blocks unloading of code which uses it from unloadable load context Apr 14, 2025
@vitek-karas vitek-karas marked this as a duplicate of #114620 Apr 14, 2025
@alexey-zakharov
Copy link
Contributor

Some tables cannot be made completely weak as they back static API that needs to behave predictably.

@ericstj sorry to resurrect the old thread 😄
Do you recall which tables cannot be made weak and why? I had to touch quite a few of those in #114619 to ensure assembly can be unloaded, and now I would like to know if this can break functionality (or which tests should be checked/added). And you were right the value should be made weak as well in some cases

@ericstj
Copy link
Member

ericstj commented Apr 29, 2025

cc @steveharter
I was worried about TypeDescriptor and TypeConverter API behaving unpredictably. It's been 5 years since making that comment, but I bet I was thinking of cases where folks register a provider through static API and expect it to work, and not get collected due to weak reference backing.

@ericstj ericstj added design-discussion Ongoing discussion about design without consensus feature-request and removed bug labels Apr 29, 2025
@alexey-zakharov
Copy link
Contributor

cases where folks register a provider through static API and expect it to work

thanks for the comment @ericstj! this is a good point, I think using WeakReference for the Type itself is ok in the context of the assembly unloadability as LoaderAllocator would keep the Type alive, however the registered provider could be collected if wrapped in the WeakReference. 🤔
If we use DependentHandle with dependency on the Type, do you think that would provide a good binding to keep registered providers alive?

@ericstj
Copy link
Member

ericstj commented Apr 30, 2025

I've never used that before, but it seems like it should. Can we guarantee the Type object will stay alive so long as the assembly is not unloaded? Is the unloading something that needs to be done explicitly, and not just something that will happen on its own when no strong references remain?

@alexey-zakharov
Copy link
Contributor

Can we guarantee the Type object will stay alive so long as the assembly is not unloaded?

I believe so. As far as I understand the design, the type instance for collectible types is allocated by LoaderAllocator and stored in its m_slots table. So it will be alive until MethodTable is alive and it is alive until the LoaderAllocator is alive.

Is the unloading something that needs to be done explicitly, and not just something that will happen on its own when no strong references remain?

unloading of the AssemblyLoadContext is explicit, yes - the AssemblyLoadContect.Unload api should be called, before that all assemblies, their code and type will remain in memory

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.ComponentModel design-discussion Ongoing discussion about design without consensus feature-request
Projects
None yet
Development

No branches or pull requests

10 participants