Guaranteed, Finalization Order Is Not

July 28, 2008

7 comments

image As part of our ongoing quest to categorize all finalization-related problems, I’d like to present another honorable mention in the series.  This time, it’s finalization order (or the lack thereof).

A quick refresher on finalization: When an object with a finalizer is created, a reference to it is placed in the finalization queue.  When the object is no longer referenced by the application, the GC moves it to the f-reachable queue.  This wakes up the finalizer thread, which in turn removes the object from the queue and runs its finalizer.  At the next GC, the object’s memory is reclaimed.

With that in mind, consider a graph of objects with finalizers, such as a StreamWriter and a FileStream.  The FileStream is a thin wrapper around a Win32 file handle and the related APIs.  The StreamWriter is a buffering convenience wrapper around a stream.  Both require finalization to work properly – the StreamWriter needs a finalizer to flush its internal buffer and close the underlying stream, and the FileStream needs a finalizer to close the Win32 file handle.  Both should also implement IDisposable or provide other means for deterministic finalization (e.g. a Close method).

So what happens if the user doesn’t use deterministic finalization and relies on a finalizer instead?

FileStream fs = new FileStream(“1.txt”, FileAccess.ReadWrite);

StreamWriter sw = new StreamWriter(fs);

sw.WriteLine(“Hello World”);

//No sw.Close() here

Scenario #1: If the StreamWriter‘s finalizer is called first, it will flush the buffer and close the FileStream.  Since the stream was deterministically closed, it will not be finalized.

Scenario #2: If the FileStream‘s finalizer is called first, it will close the Win32 file handle.  When the StreamWriter‘s finalizer is called, it will attempt to flush its internal buffer into a closed stream!

Since finalization order is undefined (and there’s no way for it to be defined, because it depends on the GC’s traversal order which is also undefined), we can never tell if we’re going to land in scenario #1 (yay) or scenario #2 (ouch).  Therefore, StreamWriter does not have a finalizer.

Bonus question: What happens if you forget to deterministically close a StreamWriter?  (You can try it at home.  Nothing horrible will happen, but you will lose the buffered data that wasn’t yet flushed to the underlying stream.)

This also brings up the subject of resurrection, a little-known “feature” that boils down to making an object accessible from within a finalizer.  For example, if we were to implement object pooling, we would want to ensure that even if the user didn’t explicitly return the instance to the pool, it will still be returned to the pool by the finalizer:

class PooledObject

{

    //Implementation omitted for clarity

    //…

 

    ~PooledObject()

    {

        Pool.ReturnToPool(this);

    }

}

The first problem with this approach is that the finalizer for this specific instance won’t ever be called again.  We get one shot at the finalization queue – no one is going to add our instance back automatically, so we have to take care of it ourselves:

GC.ReRegisterForFinalize(this);

Another problem we are certainly going to have revolves around other finalizable objects we might be referencing.  Since finalization order is undefined, it is entirely possible that the finalizers for our contained (referenced) objects have already run, rendering their state inconsistent.  Any attempt to use them will yield undefined results – few objects are willing to work properly after their finalizer has already run, or are even aware of the possibility this might happen!

Effectively, the only “safe” way of resurrecting such referenced objects that are outside our control is discarding and reinitializing them from scratch.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

7 comments

  1. pavelyJuly 29, 2008 ב 1:45 AM

    There is one way that you can (somewhat) control finalization order. If a class inherits CriticalFinalizerObject, its finalizer is guaranteed to be called after all non-CriticalFinalizerObject(s).
    Classic example is SafeHandle. For example, a FileStream holds one of those, which ensures the file handle is valid when its finalizer is run.

    Reply
  2. Alois KrausJuly 29, 2008 ב 1:59 AM

    I did solve the race condition between FileStream and StreamWriter and a solution for it if the application does exit normally.
    http://geekswithblogs.net/akraus1/articles/81629.aspx
    The trick is that there is another type of Finalizer called Critical Finalizer that can help in this case.

    Yours,
    Alois Kraus

    Reply
  3. SkupJuly 29, 2008 ב 4:47 AM

    Hi,
    I wrote an post about reader writers and IDisposable some time ago ( http://www.pixvillage.com/blogs/devblog/archive/2006/10/17/6450.aspx ).

    I you look carefully at the StreamWriter in reflector, it doesn’t hold any reference to unmanaged resources.
    And it doesn’t use the stream reference when called from the finalizer (the stream could have been finalized alreader, you should never use reference types in the finalizer !)

    So there is no danger to use a StreamWriter without disposing it. It will not close the stream when finalized.

    Reply
  4. Sasha GoldshteinJuly 30, 2008 ב 2:54 PM

    @Pavel, @Alois: Thanks for your comments – indeed, you can rely on CriticalFinalizerObject to partially alleviate the problem. (I’ve blogged about CriticalFinalizerObject before: http://blogs.microsoft.co.il/blogs/sasha/archive/2008/04/26/don-t-blindly-count-on-a-finalizer.aspx)

    @Skup: The whole point of this post was that StreamWriter does not have a finalizer *because* it would be unsafe if it had one. And there *is* a danger in using StreamWriter without closing it, because some of the buffered data will not be flushed. If losing some of your data that you thought was made durable is not dangerous, I don’t know what is.
    Additionally, I feel that it’s a bit of an overkill to say that you should never use reference types within a finalizer. There are specific cases in which there are types within your control that are safe to use because they do not have a finalizable object in their sub-graph.

    Reply
  5. SkupJuly 31, 2008 ב 6:21 AM

    Hi Sasha,

    for sure it’s easy to forget to call Flush, and I think it’s a good idea to call it whenever you’re done with using a writer (even when disposing it – in this case this is useless, but it shows your intent !).
    The Flush method is meant to be sure your data are written to stream, and the Dispose method is meant to dispose the undelying stream.
    I think is a good way to be sure of what happens.

    About references in the finalizer, the MSDN says ‘The Finalize method should not reference any other objects.’ (http://msdn.microsoft.com/en-us/library/b1yfkh5e.aspx).
    There are surely some cases where you can be sure it won’t be a problem, but I can’t see why it could be useful.

    Reply
  6. Sasha GoldshteinAugust 1, 2008 ב 10:36 AM

    Yes, the intent of this comment from MSDN is quite clear if you look at the context of the whole sentence:

    “An object’s Finalize method should free any external resources that the object owns. Moreover, a Finalize method should release only resources that the object has held onto. The Finalize method should not reference any other objects.”

    This last part implies that you shouldn’t reference objects other than resources internal to your object. However, it does not imply you should not reference any object at all.

    Reply
  7. Brad McCartneyAugust 10, 2012 ב 12:51 AM

    Thank you for the outstanding posts

    Reply