-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Cannot unload a collectible AssemblyLoadContext if JsonConvert.Serialize() has been called on a custom object #13283
Comments
This is one of the known issues with the unload in .NET Core. Caches. They tend to hold strong references to things they really should only have weak references to. There's no ideal solution other than fixing Newtonsoft.Json in this case (to use weak refs). A workaround would be to load the Newtonsoft.Json into every load context separately. You can do this by overriding the The downside of this approach is:
|
Thanks, I now tried to explicitly force Newtonsoft.Json to be loaded in collectible AssemblyLoadContext, but it did not help. I updated the
When running the sample, the console shows that Newtonsoft.Json.dll is loaded to the HostAssemblyLoadContext, but still the unload fails. I debugged the sample with dotMemory and it shows that the reference to the If I understand correctly, TypeDescriptor and ReflectTypeDescriptionProvider are loaded from the framework assembly I also tried forcing the Soo, any chance in fixing Or could there be some way of loading such framework assemblies to the collectible AssemblyLoadContexts? |
You should be able to load everything from the framework / runtime except the System.Private.CoreLib.dll into the unloadable context (or maybe just the System.ComponentModel.TypeConverter). That's something that our nightly unloadability tests do. It is not recommended for general usage, but it should solve your issue, hopefully not introducing other problems. |
Thanks, after studying the test code here (as well as some trial and error), we finally got the unload working. The trick was to load the netstandard.dll to the collectible AssemblyLoadContext, too. Otherwise System.ComponentModel.TypeConverter.dll was always loaded from the default context. Perhaps the type forwards in the netstandard assembly always point to the same load context? Both assemblies also needed to be loaded to the AssemblyLoadContext when creating it, as was done in the tests. Trying to resolve them in the I updated the sample, and now the unload works there. Our own plugin load contexts also now seems unload cleanly after the changes. We'll need to test it more to see if the explicit load of the assemblies causes causes issues, but so far everything seems to work. Thanks for all the help! |
Loading netcoreapp framework multiple times in the same process or loading it as unloadable is unsupported configuration. Many framework assemblies (e.g. networking stack) are incompatible with unloading. They won't unload, leak or crash. |
@jvuoti Brilliant workaround! thank you so much! This should be resolved in the framework tho. |
Can you point me in the direction of the tests that helped you please @jvuoti |
Sorry, don't remember the exact details anymore, but we were probably looking at the System.Runtime.Loader tests. However, we were just mostly doing trial and error, running the repro sample in a memory profiler to see where the references were being held. I think the tests pointed us to try loading the referenced assemblies explicitly to the AssemblyLoadContext on program init, which then worked. |
I ended up experimenting in the end and this works in my proof on concept, it might help anyone else with the same issue
and just invoke it on any AssemblyLoadContext you wish (in my case all of them) at creation like so
|
@rrs I am trying to get your code working but I cannot seem to get the netstandard dll loaded. It does not seem to exist in AssemblyLoadContext.Default.Assemblies. Any insights you can provide on how to get this loaded? |
@groogiam |
.netcoreapp3.1 in an asp.net core service originally but I've since moved to just trying to get it working in a console application. I was trying to use the NewtonsoftIsolationHelper above but netstandard does not get loaded by default in my netcoreapp3.1 console app. |
looked into it, and its a bit of a pain. solution below is a bit hacky but I see no other way.
|
If you execute
Newtonsoft.Json.JsonConvert.Serialize()
on a custom object defined in an assembly in a collectible AssemblyLoadContext, the AssemblyLoadContext can no longer be unloaded.We noticed this issue in our testing as we are planning to use the collectible AssemblyLoadContext feature to port our plugin code from .NET framework to .NET Core 3.0. Our plugin code currently uses AppDomains and Json.NET for serializing the parameters and results for the plugin calls. Furthermore, the parameter structure classes are defined in the plugin assemblies, so this issue now blocks our .NET Core plugins from unloading.
The unload problem seems to be caused by TypeDescriptor caching:
JsonConvert.Serialize()
internally seems to callTypeDescriptor.GetConverter(type)
on the custom object type, which adds it to the static TypeDescriptor caches. As the TypeDescriptor is loaded in the default LoadContext, the TypeDescriptor caches will keep the references to the custom types alive, and block the plugin assembly from unloading.A simple reproduction based on the Unloading sample can be found here: https://github.com/jvuoti/samples/tree/jsonconvert_blocking_assemblyloadcontext_unload/core/tutorials/Unloading
In the sample, the
Logger
plugin dependency has been modified to serialize aCustomLogMessage
object, also defined in the same plugin assembly:Once the modified plugin code is called, the sample can no longer unload the AssemblyLoadContext.
The only way we have found to get the AssemblyLoadContext to unload is to first clear the internal TypeDescriptor caches via reflection, like this:
However, this does not really feel right :)
So are we just doing things wrong, and would there be a simpler workaround for this? E.g. is there some way we could force a copy of the TypeConverter to be loaded inside the collectible AssemblyLoadContext, so the caches would also be inside it?
The text was updated successfully, but these errors were encountered: