none
GC cleans up object too early? RRS feed

  • Question

  • Is this is a CLR bug or expected behavior?
    I have the following c# code:

    {
      SomeClass theObj = new SomeClass();

      theObj.DoWork(); // note, this may take several seconds to return.
    }

    What I am seeing is that theObj is being disposed before the call from DoWork returns.
    If I add a call to GC.KeepAlive(theObj) after the DoWork call, everything is fine.
    But is seems to me that this should not be necessary (it also scares me because I am not sure how many other places in our code require this call).

    There are details that are likely to be pertinent:
    • This is a x64 build (I haven't been able to repro it under win32)
    • The DoWork method makes a call into unmanaged code, and in fact is in unmanged code when the dispose is called.
    • SomeClass is written in managed c++

    Brad Pechacek
    Friday, May 8, 2009 5:34 PM

Answers

All replies

  • Is DoWork() calling into the unmanaged code asynchronously, or is this call being handled asynchronously?

    If so, you may (potentially) have no managed references left to theObj instance, which could make it a candidate for GC. 

    Also, is this happening in debug builds, release mode builds, or both?
    Reed Copsey, Jr. - http://reedcopsey.com
    Friday, May 8, 2009 5:42 PM
    Moderator
  • The "this" pointer is on the stack while DoWork() executes.  That's enough to keep the reference live.

    Your phrase "the dispose is called" is confusing.  The Dispose() method (destructor in C++/CLI) is only ever called explicitly by your own code.  Did you mean the finalizer?

    Another angle is delegates that you pass to unmanaged code.  The garbage collector cannot track those so they won't keep your class object alive.  In other words, it will go kaboom when the unmanaged code, later, invokes the delegate.

    Hans Passant.
    Friday, May 8, 2009 5:55 PM
    Moderator
  • I guess I mean the finalizer. It looks like this:

    SomeClass::!SomeClass()

    Brad Pechacek
    Friday, May 8, 2009 7:50 PM
  • Everything here is synchronous. Although there is unrelated work going on in other threads (FileSystemWatcher notifications). It is presumably this work in other threads that is triggering the GC.

    This is happening in release builds.
    I have tried a bit and haven't seen it in debug builds.

    Brad Pechacek
    Friday, May 8, 2009 7:59 PM
  • Oh, and I am not passing any delegates into unmanged code.
    Brad Pechacek
    Friday, May 8, 2009 8:00 PM
  • There's an implicit KeepAlive in debug builds, helps to be able to inspect local variables.  As I said, the "this" reference is on the stack while DoWork() executes and the garbage collector should always find it when it goes hunting for references.  The next possibility is that the unmanaged code is mucking with the stack, destroying the this reference.  Buffer overflow or bad pointer, something like that.

    Hans Passant.
    Friday, May 8, 2009 8:08 PM
    Moderator
  • I see no evidence of the stack being corrupted. When the KeepAlive is in place, the code runs without throwing any excpetions or any other problems.

    Perhaps the garbage collector sees that the thread is running unmanaged code and doesn't bother looking at its stack.


    Brad Pechacek
    Friday, May 8, 2009 8:55 PM
  • That's not how it works.  When your code thunks from managed to unmanaged code, it pushes a special cookie on the stack.  That cookie serves as a fence for the garbage collector, preventing it from iterating the stack too deep, into unmanaged stack frames that cannot be probed for references.  The "this" reference is above that cookie.

    Your code is behaving very strangely.  You'll have to assume something strange happened.

    Hans Passant.
    Friday, May 8, 2009 9:05 PM
    Moderator
  •  Ok, thanks that is a bit helpful.

    Am I correct that there is an implicit "theObj = null;" at around the time of the call to DoWork? and that the call to KeepAlive essentially is deferring that action until after my call to DoWork?

    I can reproduce this problem quite readily in my full application, in fact I have seen the same behavior to two completely different areas of the code. I am trying now to repro it on a simpler app.
    Brad Pechacek
    Friday, May 8, 2009 10:48 PM
  • > If I add a call to GC.KeepAlive(theObj) after the DoWork call, everything is fine.

    This is a good sign that GC.KeepAlive is required.  I recall reading that the garbage collector is surprisingly aggressive in determining which stack variables can be ignored because there are no future accesses to the variable.  It would be my understanding that the "this" special variable is in no way exempt.  Consider the case where after a point there are no future references (more accurately, managed ones) to member variables thus no need for "this".

    Not sure if any of this actually applies to the original poster's application, but when using P/Invoke this is something to keep in mind.  If the unmanaged code in question interacts through handles, instead of using KeepAlive, one can also look into SafeHandles (or the older HandleRef).
    Friday, May 8, 2009 10:54 PM
  • Try to reproduce this issue in simpler solution, and post it here for us to research, this looks like a usual behavior.

    Thanks

    Marco
    Another Paradigm Shift
    http://shevaspace.blogspot.com
    Tuesday, May 12, 2009 6:26 AM
    • Marked as answer by bwp Tuesday, May 12, 2009 3:13 PM
    Tuesday, May 12, 2009 7:56 AM
    Moderator
  • Well, "this" could be reclaimed even if an instance member method is still executing, that's interesting, glad to know about that:)

    Although it makes sense for me, but just get a little bit surprised from the very beginning.

    Thanks
    Marco
    Another Paradigm Shift
    http://shevaspace.blogspot.com
    • Edited by Marco Zhou Tuesday, May 12, 2009 9:19 AM bad typo
    Tuesday, May 12, 2009 9:17 AM
  • Well, this blog entry from 2003 explains exactly what I am seeing.
    In my case, I am using managed c++ classes purely as a wrappers around unmanaged c++ objects.
    The methods in the wrapper classes are just pass throughs to methods in the wrapped unmanaged objects which do the actual work.
    The constructor of the wrapper classes create an unmanaged object; and the finalizer deletes the object.

    It looks like I will need to add a call to KeepAlive and the end of each pass-through method.


    public ref class WrapperClass
    {
      public:
        WrapperClass();
        !WrapperClass();
        void DoWork();

        // The unmanaged object we are wrapping.
        UnmanagedClass * unmanagedObject;
    };

    WrapperClass::WrapperClass()
    {
        unmanagedObject = new UnmanagedClass();
    }

    WrapperClass::!WrapperClass()
    {
        delete unmanagedObject;
        unmanagedObject = NULL;
    }

    void WrapperClass::DoWork()
    {
        unmanagedObject->DoWork();
        // KeepAlive is necessary to make sure we are not finalized
        // while we are in the DoWork call.
        System::GC::KeepAlive(this);
    }
    Brad Pechacek
    Tuesday, May 12, 2009 3:16 PM
  • But one more thing to clear up.
    Was Nobugz correct in the earlier post that the "this" on the call stack was enough to keep an object from being collected?
    Maybe this is something new since 2003 when this blog entry was written.

    Brad Pechacek
    Tuesday, May 12, 2009 3:21 PM
  • -> That's not how it works.  When your code thunks from managed to unmanaged code, it pushes a special cookie on the stack.  That cookie serves as a fence for the garbage collector, preventing it from iterating the stack too deep, into unmanaged stack frames that cannot be probed for references.  The "this" reference is above that cookie.

    I think Hans is talking about the transition frame which is pushed into the stack when doing managed to unmanaged or unmanaged to managed transition. CLR will need to demarcate between managed frames and unmanaged frames within the stack, so that he could directly bypass any unmanaged stack frame when scanning the stack for GC roots.

    I am not particularly clear how this could affect the reachability of this reference if an instance method is calling an unmanaged method, Hans, could you please elaborate further?

    Thanks
    Marco
    Another Paradigm Shift
    http://shevaspace.blogspot.com
    Wednesday, May 13, 2009 7:36 AM
  • That was just in response to the OP's assertion that the GC wouldn't walk the stack if the unmanaged code is executing.  For the record, I'm pretty unconvinced that Brumme's blog explains the problem.  Yes, local variables are subject to this.  But not when there's an actual reference on the stack.  Well, maybe there isn't one...

    Hans Passant.
    Wednesday, May 13, 2009 9:15 AM
    Moderator
  • Maybe a method clears out the "this" pointer from the stack as soon as it has finished accessing member variables. This would explain the behavior I am seeing.

    There appears to be an intricate dance when the last reference to a local variable is a method call. The order seems to be:
    1) add the reference to the stack (as the "this" pointer for the method)
    2) remove the local variable from the valid reference list
    3) call the method

    In my case I wish it would work a bit different when the call is to unmanged code. I think it should not remove the local variable reference until after the called method returns.

    If it worked that way, I wouldn't need to use KeepAlive.

    Brad Pechacek
    Wednesday, May 13, 2009 6:25 PM