Quantcast
Channel: Common Language Runtime Internals and Architecture forum
Viewing all articles
Browse latest Browse all 1710

ICorProfilerCallback::ClassUnloadStarted not called for a generic class instantiation, even though it was unloaded

$
0
0

Hi CLR forum team,

My company is developing a CLR profiler which keeps track of loaded classes based on ClassIDs, by following the ClassLoadStarted, ClassLoadFinished and ClassUnloadStarted callbacks. In some scenarios, where modules are unloaded, we've noticed that certain generic classes get unloaded, without ClassUnloadStarted being called for them. This happens only after module unloading, and only for certain generic classes. Then, when we try to query about the ClassID using any API function, thinking its still loaded and valid, the CLR crashes (unsurprisingly, since the ClassID were invalidated).

We can tell that the relevant class was indeed unloaded, by looking at its memory (ClassID address) in Visual Studio's memory view and seeing that it was released (as opposed to other, valid ClassIDs). Also, the owning module was itself unloaded.

My question: is this possible (by design) for a generic class (or any other class for that matter), in certain scenarios, to be unloaded without ClassUnloadStarted being called? Or, could this be a CLR/profiling API bug?

We've met this issue with several sites running on iisexpress, with .NET 4.7.2, and also with latest 4.8 preview (4.8.03745).

After research, we were able to replicate this behaviour with a small example project, that triggers module unloading by manually unloading AppDomains. A simple generic class was enough for the issue to occur, but not any generic class did.
Here is the example project: https://github.com/shaharv/dotnet/tree/master/testers/module-load-unload

Below is the original scenario that we've faced:

- The class in question is IComparable<ClassFromModuleFoo>.
- The load/unload callbacks flow, based on debug prints added in the Load/Unload callbacks:
   1. IComparable<ClassFromModuleFoo> (mscorlib) is loaded.
   2. The class ClassFromModuleFoo is loaded into assembly #1.
   3. Module Foo finishes loading into assembly #1.
   4. Then, module Foo is loaded again into a different assembly, #2.
   5. IComparable<ClassFromModuleFoo> and ClassFromModuleFoo are loaded again, this time in assembly #2. Now there are two instances of each class: one in Foo loaded in assembly #1, and one in Foo loaded in assembly #2.
   6. Module Foo begins to unload from assembly #1.
   7. ClassUnloadStarted callback is called for ClassFromModuleFoo in assembly #1.
   8. Module Foo finished to unload from assembly #1.
   9. ClassUnloadStarted is *not* called for IComparable'1<ClassFromModuleFoo> of assembly #1 anytime later, even though its module unloaded and its ClassID points to now thrashed memory.

Note, our ClassUnloadStarted callback implementation is quite simple, namely unregistering the unloaded ClassID from the relevant data structures.

Please let me know if there is any additional information that my be helpful.

Thank you!


Viewing all articles
Browse latest Browse all 1710

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>