Wednesday, April 12, 2006 11:52 PM codingsanity

Critical Finalizers (Reliability Part 1)

For those of you who read the article at sadeveloper, apologies. From what I can see, the article got a much lower distribution than the post about the article. So, I don't want to annoy what few readers I have, so here's the whole thing again:

In this article I'll look into the problems with the standard finalizer model, explore Constrained Execution Regions and how they can help us obviate these problems. Finally I'll delve into the CriticalFinalizerObject and how it implements CERs, and adds extra "help" for important finalizers.

Okay, before we delve into the murky world of Critical Finalizers, let's start by looking at what's wrong with normal finalizers. The simple answer is "not much", the more correct answer is "not much if you don't care about it always running". I'm sure many of you have had it drilled into you that finalizers are there to ensure that unmanaged resources are correctly disposed of, even if the idiot using your class doesn't call the Dispose method.

All well and good, but they won't always run. Which means that your unmanaged resources won't always get released. And therefore you'll sometimes get leaks. If you're writing a desktop app, then maybe you don't care, but if you're writing server-side stuff you sure as hell should.

Why won't Finalize run?

Well, there's a few reasons. Basically it all boils down to how finalizers are executed. Basically when you implement a finalizer on your class .NET registers it in a queue of finalizable items (the RegisteredForFinalization queue). During normal garbage collection, if your finalizable object is no longer referenced, the CLR will move your finalizable object to the ReadyToFinalize queue. Then, after a few other GC-related tasks, the Finalizer thread dequeues each finalizable object in this queue in turn, and executes the Finalize method.

All well and good. Besides telling us that Finalizers are expensive beasties (especially if you dig into that "few other GC-related tasks" thing), what's the problem? Well, if the process is terminating, the system will timeout the finalizer thread if it cannot finalize all the items in a reasonable amount of time. In this case your finalizer may not be called. Even if the finalizer thread does get to call your Finalize method there's no guarantee that any or all of your code will run.

All sorts of things could still go wrong. The finalizer thread may be timed out during the call to your finalizer. If the process is scarce on memory, its possible that the Just In Time Compiler won't have enough memory to compile your finalizer. It may have enough memory to JIT your Finalizer, but when you make a method call inside the finalizer, it may not be able to JIT that, or the runtime may not have enough stack space for the method.

In all these cases there is the possibility that your application will leak. Now, Windows is pretty good at cleaning up after process shutdowns, but what if we're just talking about an AppDomain shutdown? Perhaps your AppDomain is hosted in a high-availability process such as SQL Server 2005. In that case any intermittent leaks can cause massive problems down the line. Especially when you consider that SQL like to run near the edge of memory exhaustion, and loves aborting threads that take too long.

So what can we do?

Well, there's a new concept in Whidbey known as Constrained Execution Regions. What a CER does is ensure that the runtime will not throw an asynchronous exception inside that region. Asynchronous exceptions include: ThreadAbortException, OutOfMemoryException and StackOverflowException. The reason that they're referred to as asynchronous exceptions is that they could happen anywhere in your code. There is no way you could ever know where a ThreadAbortException could happen. An OutOfMemoryException could occur anywhere where an object is created. StackOverflowException could happen on any method call (and a few other places besides).

The CLR goes through quite a few hoops to accomplish this. First of all it pre-JITs the code in the CERs, and will also pre-JIT all the code the CER may call (if it can determine this). However, it is up to the developer to ensure that certain other conditions do not occur in the CER. As such you are not allowed to use the following in the CER: explicit allocations, locks, boxing, accessing multidimensional arrays, calling methods through Reflection, any security checks other than link demands, typeof and casts, accessing fields on a transparent proxy, serialization, and using delegates or function pointers. Whew, what a list!

So, given that you pretty much don't do anything interesting, the runtime will guarantee that your CER won't be unexpectedly exited.

So how do I define a CER?

Okay, it's quite simple really. All you do is call RuntimeHelpers.PrepareConstrainedRegions just before a try block. Now, the try block itself is not a CER, but its finally or catch block is. Why this is, I really do not know. So, any code running the catch/finally block will not suffer from asynchronous exceptions. Please note that you should never do anything that may cause a StackOverflowException in the try block itself. Most implementations I've seen of CERs using PrepareConstrainedRegions have an empty try block:

RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally {
 // CER
}

This is actually incredibly simple. A lot easier than remembering that huge list of no-nos (or even knowing what they all mean). Even easier, you can simply add a ReliabilityContractAttribute to your method with Cer.Success or Cer.MayFail. The Consistency enumeration defines exactly what it will mean if your method does wind up failing for whatever reason. Basically what happens here is that the runtime will do the PrepareConstrainedRegions call for you.

Great, so you've defined a CER. Wonderful. Now you can....

Not so fast! Here's a thought. What if your CER calls a virtual method on a member? The runtime can't determine at type load which virtual method you'll wind up calling. As such, it can't do all the funky preparation work its just done for the CER, and you might wind up with one of those nasty asynchronous exceptions after all.

Well, you have a couple of options here. You could decorate all the virtual method instances that you might call with the PrePrepareMethodAttribute. Alternatively you can explicitly call RuntimeHelpers.PrepareMethod. Note that this must be called before you enter the CER, otherwise its not doing much good is it? So ideally you'd call this just as soon as you had a reference to the member object. Calling it when the object containing the virtual method is assigned to your class is a good idea.

Right, now we're ready to proceed. So, we happily go along and slap a reliability contract on our finalizer and make sure all its code fulfills the CER requirements and also ensure that all the methods it will call are prepared.

What about CriticalFinalizerObject?

Okay, now that we've got all that out of the way, let's return to the CriticalFinalizerObject. This is a nice simple base class that you can inherit from that puts all that CER stuff around the Finalize method for you. It's still up to you to ensure that you behave correctly within the Finalize method, but you don't have to worry about setting up the ReliabilityContractAttribute. Just as an aside, CriticalFinalizerObject initializes this attribute with the Consistency.WillNotCorruptState and Cer.Success enumerations, so it's up to you to ensure that you fulfill this contract. In other words you must guarantee that the finalization will complete, and that the finalizer will not leave the object (or anything else) in an inconsistent state.

Now, there's one more little caveat about finalizers. There's no guarantee in which order they'll run. This may not be a problem for you, but in some circumstances it can be. An example commonly cited is that of file handles and buffers. Ideally on a finalize, the buffer should write its remaining contents to the file. Obviously, when the file handle finalizes it should close the file. Now, what if the buffer finalizes after the file handle is finalized? We're suddenly stuck with the situation where the buffer is trying to write to a file that is closed.

To address this the CriticalFinalizerObject adds an extra level of reliability to our finalizers over and above CERs. Simply put, critical finalizers execute only after normal finalizers are executed. So in our example, we would make the buffer a finalizable object, and the file handle a CriticalFinalizerObject. The buffer finalizer is now guaranteed to be run before the file handles' finalizer. Well, not really. Since the buffer finalizer is not a critical finalizer, there is no 100% guarantee that it will actually run. But we can guarantee that it won't finalize after the file handle finalizes.

Conclusion

So, we've had a look (in probably more detail than was strictly necessary) at some of the problems with finalizers. We've also had a look at Constrained Execution Regions and Critical Finalizers and we've examined the various requirements for writing code that will run in a CER. If you want to delve into scary detail about finalization, the absolute best resource anywhere is Chris Brumme's excellent Finalization post, from which much of the information in this article is sourced.

If you'd like to read more of my rants, raves and suchlike you can find me at my blog. You can always email me at My Email Address

Comments

# SafeHandle (Reliability Part 2)

Friday, May 26, 2006 5:40 PM by Coding Sanity

Right, now that we've got the basics of reliability covered, we can move on to SafeHandle. If you've...