11

Given the following:

GC.Collect(GC.MaxGeneration);
GC.WaitForPendingFinalizers();
GC.Collect(GC.MaxGeneration);

Taking into account multi-threading and Garbage Collection modes, under what circumstances would you get a deadlock on WaitForPendingFinalizers?

Note: I am not looking for answers about the reasons why you shouldn't be calling GC.Collect.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Tess
  • 111
  • 1
  • 3
  • 2
    Is this a strictly preemptive question, or are you experiencing an actual deadlock situation? (If you're troubleshooting, it would be helpful to post the code for your finalizer method(s).) – Cody Gray - on strike Dec 08 '10 at 11:02

3 Answers3

6
// causes a deadlock when built with release config and no debugger attached
// building in debug mode and/or attaching the debugger might keep
// badIdea alive for longer, in which case you won't see the deadlock
// unless you explicitly set badIdea to null after calling Monitor.Enter

var badIdea = new BadIdea();
Monitor.Enter(badIdea);

GC.Collect(GC.MaxGeneration);
GC.WaitForPendingFinalizers();
GC.Collect(GC.MaxGeneration);

// ...

public class BadIdea
{
    ~BadIdea()
    {
        lock (this)
        {
            // ...
        }
    }
}
LukeH
  • 263,068
  • 57
  • 365
  • 409
5

You won't experience any kind of deadlock situation when calling GC.Collect and GC.WaitForPendingFinalizers unless you are accessing managed objects within your Finalize methods. Calling methods of other objects with a public scope could potentially lead to unpredictable results, including a deadlock. The reason is that you're not in total control of those other objects' locking patterns. It could be locked by anyone while your finalizer method is trying to access it.

Additionally, locking this explicitly in the finalizer is almost guaranteed to cause a deadlock, as LukeH's answer demonstrates. You can read Jeffrey Richter's original article on that here.

In general, you should only be releasing unmanaged resources in your finalizers, which should alleviate any such concerns about deadlocks.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • Actually, there are some situations where it may be useful to have finalizable objects hold references to other managed objects. For example, because `Bitmap` objects are expensive, a program which needs to store hundreds of 16x16 icons might define a class that allocates 16x1024-pixel bitmaps, each of which could hold 64 icons, and then returns objects each of which holds a reference to a bitmap-allocator along with an indication of which portion it 'owns'. When such a 'bitmap piece' object is disposed, the allocator can make that portion of the bigger bitmap available to someone else. – supercat May 13 '12 at 21:19
  • Even if the only strong references to the larger bitmap were within the 'bitmap piece' objects, so that abandoning all such objects would make the larger bitmap eligible for finalization, abandoning all but one of the 'bitmap piece' objects associated with a bitmap would leave the whole thing allocated but mostly unavailable for other purposes. Adding a finalizer to the bitmap-piece object would make it possible for the master object to reuse pieces that were abandoned. Of course, such code must be written carefully to avoid deadlocks or other threading woes, but it can still be useful. – supercat May 13 '12 at 21:23
  • I don't understand what that has to do with this answer or question. @supercat – Cody Gray - on strike May 14 '12 at 17:35
  • I was reacting to your last statement, though on re-reading I notice it says "In general...." My point was that there are situations where it can be appropriate for finalizers to interact with managed resources. – supercat May 14 '12 at 17:42
4

There is famous deadlock on WaitForPendingFinalizers, described by Jeffrey Richter. It is shown here: http://haacked.com/archive/2005/04/12/neverlockthis.aspx

class MyClass
{

private bool isDisposed = false;

    ~MyClass()
    {
        lock(this)
        {
            if(!isDisposed)
            {
                // ...
            }
        }
    }
}
...
MyClass instance = new MyClass();

Monitor.Enter(instance);
instance = null;

GC.Collect();
GC.WaitForPendingFinalizers();

It is caused by incorrect using of lock(this). Anyway, this is situation when WaitForPendingFinalizers is locked.

Alex F
  • 42,307
  • 41
  • 144
  • 212