question about .NET premature finalizer race-condition "solved" by SafeHandle RRS feed

  • Question

  • In this 2005 article on the reason for the .NET v2.0 SafeHandle construct a GC finalizer race conditions is discussed.

    If I understand it correctly, it explains that a call to an instance method does not hold a GC reference to a "this" pointer for the duration of the call. Thus it is possible for an object instance to be GC finalized in one thread, while another thread still has code running inside the instance method -- if that instance method has no more references to it's "this" pointer. 

    This presents a particular problem for methods with calls to PInvoke entry points, as the thread may be inside unmanaged code using a resource when the Finalizer closes that unmanaged resource.

    My question is:

    Why would a call to an instance method not hold "this" alive for the entire duration of the call? 

    It seems this could happen in more cases than just PInvoke. This seems to allow a finalizer to run before an object is "done being used" anytime the object instance is not stored. (i.e. inside a statement of the form "new Foo().Method();") If the finalizer triggers an IDispose pattern, this could cause a managed resource to be torn down before it's done being used as well.

    Why isn't this considered a bug in the .NET runtime? 

    For example, I would think the following code fully managed could have the same race condition occur, where mo.Dispose is called before mo is done being used.

    class Foo : IDisposable { ManagedObject mo = new ManagedObject(); ~Foo() { this.Dispose(); } public void Dispose() { if (mo != null) { try {mo.Dispose();} finally { mo = null; } } } void Problem() { mo.doSomething(); } static void Main() { new Foo().Problem(); } }

    Friday, August 24, 2012 5:29 PM


  • The issue is because the optimizations in the JIT "see" that the object is no longer being used - but they can't see into native code.  

    In your case, "this" is always effectively referenced - the JIT can see that the instance isn't done being used until after Problem() completes - but if Problem() were to call into P/Invoke, and the native code did something that kept using its members, there would be no way for it to "see" that happening.

    It can't happen with pure managed code because the runtime knows about all managed objects - but the native objects are kind of going into a black hole where anything can happen, and the runtime can't track what's happening to those handles on the native side.  That's where the problem can occur.

    Reed Copsey, Jr. -
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    Friday, August 24, 2012 5:37 PM