ExternalFinalizer: Adding a finalizer to 3rd party objects
Let’s say that you have the following scenario, you have an object in your hands that is similar to this one:
It holds some unmanaged resources, so you have to dispose it.
However, this is used in the following manner:
What is the problem? This object may be used concurrently. In the past, the frame was never updated, so it was safe to read from it from multiple threads. Now there is a need to update the frame, but that is a problem. Even though only a single thread can update the frame, there may be other threads that hold a reference to it. That is a huge risk, since they’ll access freed memory. At best, we’ll have a crash, more likely it will be a security issue. At this point in time, we cannot modify all the calling sites without incurring a huge cost. The Frame class is coming from a third party and cannot be changed, so what can we do? Not disposing the frame would lead to a memory leak, after all.
Here is a nice trick to add a finalizer to a third party class. Here is how the code works:
The ConditionalWeakTable associates the lifetime of the disposer with the frame, so only where there are no more outstanding references to the frame (guaranteed by the GC), the finalizer will be called and the memory will be freed.
It’s not the best option, but it is a great one if you want to make minimal modifications to the code and get the right behavior out of it.
We have scenario in which this might help. In our code we store UnityContainer instance in MemoryCache and when it is removed - we dispose it. Unfortunately item from the cache can be removed not only due to expiration, but also when there is memory pressure. Which might leave us with disposed object which is used at the moment. Can you advice us to use approach from the article, or you know something better?
In the case of
MemoryCache, you have the
RemoveCallbackwhich you can use to handle this logic, see: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.caching.cacheitempolicy.removedcallback?view=dotnet-plat-ext-7.0
We use it, but it might fire for 2 reasons. After expiration and it is safe to dispose, but also when memory is too high - then object might be shared across your live code.
Item can be "Evicted" from cache: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.caching.cacheentryremovedreason?view=dotnet-plat-ext-7.0
Oh, I see.
In your case, yes, you will add the external finalizer when the item is removed from the cache, and then dispose it when there are no more references outstanding. That is a good use case for this technique, absolutely.