Why does my WeakReference throw an InvalidOperationException when I use it from my finalizer?

I saw this answer from Brian Grunkemeyer, a dev on the CLR team, recently… I thought it would generally useful… let me know what you think?



A: The reason we throw an exception is pretty simple – WeakReferences are finalizable objects, just as your objects (TextChunk) is finalizable.  The CLR provides no ordering among finalizers.  This is a big departure from C++’s destructors, and basically means that if you write a finalizer, you should not count on being able to use other finalizable objects at all.  The GC is free to run your WeakReference finalizer before your TextChunk finalizer – that change is within the established contract for finalization, and is not a bug in the CLR.  Your code must deal with this.  (There are two other cases that could cause this – your constructor could throw an exception, letting you finalize a partially constructed object, or you ran into some interesting behavior on appdomain unloads or process shutdown.  Yet all these cases are your problem.) 


You have two options here: 


1)       Write your finalizer to deal with a WeakReference that may have been finalized.  You must both check to see if the WeakReference’s Target property returns null AND you must also put a try/catch around your code using the Target property, to catch an InvalidOperationException (please catch just this exception – catch(Exception) is evil for other reasons).  Then if you don’t get back a valid object from your WeakReference, write your code to handle this case.  In the future, you may need to do some synchronization here as we may add in multiple finalizer threads, so if the target of the weak reference is also finalizable, you may have some very tricky synchronization issues when running on a future version of the runtime.

2)       Use our IDisposable pattern, where your Finalize and Dispose() methods call a private or protected Dispose(bool disposing), setting the Boolean to true from the Dispose() method and false from the Finalize method.  From Dispose(bool), you can safely touch other finalizable objects if and only if the disposing parameter was true.  Then you simply write your code to not touch your WeakReference at all if called from your finalizer.


#2 is vastly simpler, and is the design pattern we’ve gone with for the vast majority of Framework types.  I don’t know of anyone who is trying to make #1 work and has succeeded in V1\V1.1.  And I don’t know if their code will be correct in the presence of multiple finalizer threads.


If you need a better understanding of this, you might consider reading a section of Jeffrey Richter’s Applied Microsoft .NET Framework Programming.  Chapter 19 discusses the GC, and in particular pages 471 through 489 cover the Dispose pattern, C#’s using statement, and WeakReference.  The OSHandle class he proposes in there is a primitive version of the SafeHandle class we are working on for Whidbey.  Additionally, after you understand all of this you might want to look at Environment.HasShutdownStarted, so you can tell whether any of the objects held in static variables may have been finalized, which happens somewhat late during process shutdown and appdomain unloads.

Comments (7)

  1. Nicholas Allen says:

    Should option #1 be so hard (or potentially impossible) to perform? Or, better, why isn’t the correct option #2 easier to discover in this situation? Option #2 only looks simpler if you know about this failure mode. Option #1 can appear very tempting and probably works often enough that people would be fooled into thinking it worked all the time.

  2. Giampiero says:

    Why does this kind of stuff never make it to the MSDN documentation? I would love to see a "things you should know" in the Visual Studio help that covers all the stuff you guys tend to cover in blogs and books.

  3. Li Jianzhong says:

    Why not remove the WeakReference’s Finalize, while just provide a Dispose method?

    Is there any enough reason to make WeakReferences finalizable objects? After all, if I forget to dispose the WeakReference object, there is NO resource leak (Although WeakReference hold a handle internally, the GC is free to collect the WeakReference.Target at anytime, right?)

    BTW, why not make the WeakReference a sealed class?

  4. William Robertson says:

    Just plugging the book: Applied Microsoft .NET Framework Programming. It really was the best .net book I have read.

  5. Brian Grunkemeyer says:

    We cannot remove the finalizer from a WeakReference because it allocates a resource itself. I believe the GC allocates a slot in an internal table for the weak reference, and this slot must live for the lifetime of the WeakReference object. If the target goes dead, we need that slot to still be alive until the WeakReference is finalized, or you could use the Target property on the WeakReference object and potentially get a different object back, if someone else allocated a WeakReference that reused that slot. So WeakReference needs to be finalizable. Granted, this table that the GC keeps is per-appdomain, so on an AD unload, we are guaranteed to clean up the weak reference. But you can still have a slow leak that takes down a long running process that only uses one appdomain if we didn’t provide a finalizer on WeakReference.

    You might be able to allocate a GCHandle to work around this, if you specify the type of the GCHandle to be a weak handle. But you’d have to know to do this, and you’d have to guarantee that your GCHandle gets freed. This is harder than it may initially appear.

    About why people can’t write correct code themselves – a lot of this is complicated by our reliability story, which honestly hasn’t been fully thought through until our version 2 release (we’re not necessarily "unreliable" in V1, but out of memory and other situations really aren’t handled well at all). As you may have gathered from reading Chris Brumme’s blog entries on reliability, there’s a ton of work we’re doing to guarantee cleanup of unmanaged resources in version 2. We’ll use SafeHandles for cleaning up OS handles, with a new type of finalizer that is more constrained in what it can do, but is guaranteed that we can always run it (assuming it doesn’t allocate), even in out of memory conditions. There are too many very subtle reasons why finalizers won’t always work. Don’t get me wrong – they may work more than 99.5% of the time now, or effectively all the time in most desktop apps & some servers. But we need stronger guarantees if we’re going to host the CLR in long-running servers like SQL Server that expect to have a single process up for a year, and we can’t get there easily without additional restrictions or hoops on existing code. If you’re at all interested in this, look around for anything we may have publically stated about constrained execution regions. You’ll probably only hear about CER’s on Chris Brumme’s blog though, and our hope is the vast majority of customers don’t need to know they exist. It’s the closest we’ve come to shoving rocket science in the face of the managed developer, and while it’s a great tool, it’s a somewhat complex solution for a very complex problem. (This may also impact our Disposable pattern, etc. We’ll try to get Jeffrey Richter in the loop here so the next edition of his book will have the most current information.)

    Beyond that, we may to possibly add multiple finalizer threads at a future point in time. Hence, we need to adjust the contract of finalization to allow us to potentially fix performance or scalability issues in the future, requiring that you make your finalizers insulated from each other (not exactly threadsafe, but independent enough from each other that they are effectively threadsafe, or if they do write to shared state, they must use locks). That being said, I don’t think we’re going to add multiple finalizer threads in V2, but this may change after we do more stress testing with SQL Server on 32-way machines. (If you have 32 processors allocating finalizable objects and one of those processors also runs just one finalizer thread, perhaps you can fall behind allocations. At that point, do you throttle all allocations, or do you inject multiple finalizer threads to attempt to keep up? I don’t think what the best approach is yet. But we’d like to ensure the programming model for managed code doesn’t prevent us from choosing adding more finalizer threads.)

    BTW, WeakReference should indeed be sealed. I can’t explain why it isn’t. Probably oversight – this class was written once then half-rewritten by someone else in V1. Perhaps we should consider changing this.

    Also, I’m not sure why this stuff never makes it into MSDN. Most of this is arcane knowledge that you only get from building the next version of a product. Blogs are an easier way to share this information, but that requires that you know they exist and you care enough to read the blog. (Additionally, some of the details of what we require may change by the time we ship.) MSDN would be a better mechanism. Hopefully we’ll include some white papers on writing reliable managed code in another version.