1

I'm running into a problem where it would appear the GC thread is waking up and deleting an object while it's in use.

While processfoo is running, and before it returns, it would appear fooCopy destructor fires in a separate thread. Has anyone seen a problem like this and if so how do you work around it? Surely, this can't be really happening and I must be doing something wrong. Can anyone give me some tips/strategies to debug the garbage collection?

try
{
    CFoo fooCopy = new CFoo(someFoo);
    someBar.ProcessFoo(fooCopy);
}

CFoo has an IntPtr member variable. ProcessFoo passes that member variable to an imported function from a C++ DLL that might take a few seconds to run.

In my C++ DLL I log when the IntPtr is created and when it is deleted and I can see a separate thread deleting the IntPtr while ProcessFoo is running.

tdemay
  • 649
  • 8
  • 23
  • 2
    It's completely possible as long as `ProcessFoo` doesn't need `fooCopy` anymore. – MarcinJuraszek May 04 '15 at 17:50
  • `CFoo` has a destructor/finalizer that you wrote and you end up there? (i.e. how do you know that the destructor was called) – crashmstr May 04 '15 at 17:51
  • You'll need to show more about what is going on in `ProcessFoo` for anyone to determine what is going on. – Steve Mitcham May 04 '15 at 17:53
  • ProcessFoo is attempting to access members of fooCopy and the object becomes invalid and program crashes. I logged when constructors and destructors fire. I look at the log when it crashes and see the constructor fire on my thread and then on another thread I see the destructor has fired. I am not logging the address of the object to make sure it is in fact my object, because I do not know how to do that in C#. But I can't see how it's possible to be another object that's getting deleted. I'm sure I'm doing something wrong. So I'm hoping someone can give me some trouble shooting tips. – tdemay May 04 '15 at 17:59
  • 1
    Are you able to post the implementation of `ProcessFoo()`? – jdphenix May 04 '15 at 18:02
  • Not really... It's huge. However, I think that is really off topic as I'm really trying to gain information on the garbage collection and not asking for help troubleshooting ProcessFoo. Trust me when I say I know for certain that ProcessFoo is acting on an object that is deleted and that object is getting deleted by the garbage collection in another thread while ProcessFoo is running. What I'm not certain of is that the object ProcessFoo is acting on is the same object as fooCopy, but it appears to be. I want to pursue that possibility and I'm looking for tips on how to do so. – tdemay May 04 '15 at 18:09
  • @MarcinJuraszek, can you elaborate on that? ProcessFoo does need fooCopy. How does the GC determine it isn't needed? Is there something I should to do to let the GC know it's needed? – tdemay May 04 '15 at 18:11
  • @jdphenix, What you probably need to know about ProcessFoo is that it invokes an imported function from a C++ library and passes an IntPtr member from CFoo into it. – tdemay May 04 '15 at 18:16
  • This is normal, the GC has no hope of discovering that the IntPtr is actually in use elsewhere. Declaring the pinvoke function with HandleRef instead of IntPtr is a workaround. So is appending GC.KeepAlive(fooCopy). – Hans Passant May 04 '15 at 18:31
  • I don't think the GC collects the object; as long as there's a reference to it on the stack (and in this case there are at least two), it is reachable, so it shouldn't be collected. The problem must be somewhere else. – Thomas Levesque May 04 '15 at 18:32
  • 1
    @ThomasLevesque If there is a valid reference to an object in scope, but the runtime can prove it's never accessed again, it can in fact be collected. – Servy May 04 '15 at 18:35
  • @tdemay you seem to have a C background. Let me warn you then that writing a finalizer in .NET is very rare and quite hard (as you found out). There is no deterministic finalization in .NET. The finalization order is undefined. Explore other options first. – usr May 04 '15 at 18:36
  • @Servy, good point, I had forgotten about that – Thomas Levesque May 04 '15 at 18:37
  • @usr, you are absolutely correct. I'm a C/C++ developer. I have created a library with an object model I want to make accessible to C#. I have mirrored the object model in both C++ and C#. I have created exported C methods to export creation and deletion of these objects and exported C methods that take the object pointer and invokes the class methods on them. Are you aware of a more standardize approach to this that I should be using instead? – tdemay May 04 '15 at 18:42
  • @tdemay hard to say without details. Maybe you should ask a question with how to best expose your particular object model to .NET. Leave a link here. – usr May 04 '15 at 18:44

2 Answers2

5

Surely, this can't be really happening and I must be doing something wrong.

Nope, it can absolutely be happening (this is why writing finalizers is hard, and you should avoid them where you can, and be very careful when forced to write them). An object can be GC-ed as soon as the runtime can prove that no code will ever try to access it again. If you call an instance method (and never access the object instance any time after that invocation) then the object is eligible for collection immediately after the last usage of this from that instance method.

Objects that are in scope (say, for example, the this variable of a method) but that are never used again within that scope are not considered rooted by the GC.

As for how you work around it, if you have a variable that you want to be considered "alive" even though it's never accessed in managed code again, use GC.KeepAlive. In this case, adding GC.KeepAlive(this) to the end of ProcessFoo will ensure that the object in question stays alive until the end of the method (or if it's not that method's responsibility, have the caller call GC.KeepAlive(someBar) right after ProcessFoo).

See this blog post for more information on this topic, and a few related and even more unusual properties of finalizers.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • just for clarification, isn't fooCopy what I need to pass to GC.KeepAlive right after ProcessFoo, not someBar? fooCopy is getting collected while ProcessFoo is running. If you truly meant someBar, can you elaborate on why it's not fooCopy? – tdemay May 04 '15 at 18:46
  • 1
    @tdemay It's whatever it is that you want to keep alive, that isn't staying alive. If `fooCopy` is what's being finalized that you still want to be working on, then that's what you should pass to `KeepAlive`. – Servy May 04 '15 at 18:47
2

This is pretty normal when you interop with C++, the GC has no hope of discovering that the IntPtr is in use anywhere else. It is not a reference type that the GC has awareness of, nor can it probe the stack frames of native code. The jitter marks the fooCopy object reference in use up to the underlying CALL, not beyond that. In other words, it is eligible for collection while the native code is executing. If another thread triggers a GC then it is sayonora.

You'll find details about the lifetime of local variables in this post.

There are several possible workarounds for this, albeit that the correct one can't be guessed from the question. Beyond the SafeHandle classes, very good at ensuring the finalization is taken care of as well, you could use HandleRef instead of IntPtr in the [DllImport] declaration. Or append GC.KeepAlive() to this code to force the jitter to extend the lifetime of fooCopy.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536