Mixed Types and Finalization

After I wrote about Mixing Native and Managed Types in C++ I received some feedback that my AutoPtr ref class really needs a finalizer. I had hoped to leave a discussion of finalizers to another day but since you insist…

:)

To fully appreciate the state of resource management in Visual C++ 2005 you need to understand the history and reasoning for why the CLR does not provide deterministic finalization. Brian Harry wrote an excellent post on this topic back in October 2000. If, like me, you spent many blessed hours in the 90s basking in the glory that is apartments, contexts, threading models and IUnknown, then you should appreciate this. He also has a blog but he didn’t get far with that. Here’s hoping he’ll blog some more!

“I am convinced that substantial programs can be reasonably written and debugged without the system providing any automatic support.  That said, I fully agree that it would be better if the system/language provided additional support.  Without it, you must build the behavior into the contract of the objects (like calling the Dispose method).”

The good news is that although the system, in other words the CLR, does not provide any automatic support, Visual C++ 2005 does provide language support for deterministic finalization in the form of C++ destructors that implement the Dispose pattern, although lets hope we can get this bug fixed soon! You can read my MSDN article for an introduction to C++/CLI and how memory and resource management are represented in Visual C++ 2005.

CLR reference types (called ref classes in C++) may provide an implementation of the IDisposable interface to indicate to callers that instances may be holding onto resources that need to be released deterministically. CLR reference types authored in C++ can provide an implementation of the Dispose pattern simply by providing a destructor. C++ also hides the call to the Dispose method behind stack semantics for local variables, automatic destruction of member variables and operator delete for handles.

So what are finalizers for? Finalizers provide a final safety net for objects whose Dispose method was not called. Think of it as an added layer of defense in the pursuit of more robust applications that can run longer without locking up system resources or running out of memory. If you are a C++ programmer this may sound very much like compensating for sloppy programming. Admittedly this is a bit harsh, but having finalizers release resources like file handles and database connections can adversely affect the user experience and create very badly behaving programs.

As I write my own applications, I consider failure to dispose of a managed object to be the same as failure to delete a native object, so using a finalizer does not come into play since I would just be compensating for a bug in my code. Finalizers also come with relatively significant costs at runtime. Besides their non-deterministic nature, every object with a finalizer uses up additional resources as the CLR needs to track finalizable objects. Even suppressing finalization (which C# programmers can do programmatically with the GC.SuppressFinalize method and C++ programmers get for free from the compiler) doesn’t mitigate this cost. It merely reduces the likelihood of the finalizer being run.

Another problem with finalizers that is often overlooked is related to their non-deterministic nature. The CLR runs finalizers on a background thread (and likely multiple threads in future versions of the CLR). Most developers assume this will occur some time after the object “goes out of scope”. In fact the CLR is free to aggressively finalize an object before the object’s last method call has completed forcing thread synchronization to be employed in some cases.

Writing code that will be used by others deserves extra care however. Developers writing libraries should account for the fact that applications may be written that don’t dispose/delete library objects and finalizers can help to provide this safety net. This can be especially important for resources that the operating does not automatically reclaim when a process terminates and, in the case of hosting environments, when an app domain is unloaded.

Certainly there are other improvements that can be made to AutoPtr. One potentially significant problem is that the CLR is not aware of the memory cost of the native object. It only sees a native pointer (either 4 or 8 bytes depending on platform) and so this really doesn’t help the CLR in determining whether or not there is in fact sufficient memory pressure to warrant a garbage collection. Version 2.0 of the .NET Framework provides a way of giving the CLR the hints necessary to make an informed decision. The AutoPtr class could for example use the AddMemoryPressure and RemoveMemoryPressure static methods from the GC class to inform the CLR of the size of memory used by the native object.

Here are some additional resources that you may find helpful:

Brandon Bray just wrote a discussion of mixed types and referred to my original post on mixing native and managed types.

Chris Brumme’s blog also has a lot of great posts if you’re looking for further insight into the design of the CLR.

Joe Duffy posted an article on the Dispose pattern and finalization which includes annotations from a number of the CLR architects.


© 2005 Kenny Kerr

3 Comments

  • Thanks Nish, that’s a reasonable solution provided the caller has the source code and can opt out of it if he/she doesn’t wish to pay the price of having another finalizable object in the system. Ideally the compiler would include some kind of assertion in debug builds by default for all ref classes with destructors and remove no-op finalizers entirely from release builds. The latter could also be a pre-JIT or JIT optimization.



    As I said, I prefer to avoid finalizers in types I use internally but generally adopt them for library code where necessary to avoid resource leaks in third party applications.

  • Hmmm... I'm still not sold. :)

    My use case is the desire to use a native type as a private data member of a reference type. So, I wrap it in an AutoPtr. It seems this implementation choice should not affect the public interface of the reference type. However, if the code using the reference type chooses to leverage garbage collection rather than Dispose of the reference type (which seems like a valid choice given it is a managed reference type), the AutoPtr Finalizer is hit and asserts. That is, my choice of a native type in the private implementation has imposed a requirement to Dispose of the reference type on the reference type's customers. That doesn't seem good.

    So, I implemented !AutoPtr to do the same thing as the destructor (no assert). BUT: Now my app has an AccessViolationException during exit when the Finalizer executes and I'm not sure why... It happens during the native type destruction. The native type has std::wstring members and it's while the first one's destructor is running (in _Container_base::~_Container_base()).

    The afore mentioned reference type is dynamically allocated and is an item in a Forms::ListViewItem of the application.

    Is the native heap gone by this point in the application's life cycle and therefore ~wstring()'s attempts to deallocate native heap memory blows up?

    The alternative seems to be to force Disposal of the reference type. But, now what was the point of the AutoPtr if I've really just moved the burden to remember to "clean up" to some other code?

    Thanks for any advice. Keep the useful columns coming!

  • Ryan: you can get the latest version of AutoPtr here.
    As I mention there, feel free to remove the assertion if you don’t care about deterministic finalization. The AccessViolationException you’re seeing is not directly related to AutoPtr and you’ll need to debug your app to determine where the program is dereferencing a bad memory address. The GC can happily call the AutoPtr finalizer as your process is terminating without side effects.
    > But, now what was the point of the AutoPtr if I've really just moved
    > the burden to remember to "clean up" to some other code?
    The AutoPtr class is not designed to remove the burden to “clean up”. The “burden” to clean up resources remains whether you use C++ or C#, native or managed code. AutoPtr is there to assist in writing exception-safe code and allow member variables to be destroyed by the containing destructor as in classic C++.

Comments have been disabled for this content.