views:

111

answers:

4

I have a Windows Forms app that itself launches different threads to do different kinds of work. Occasionally, ALL threads (including the UI thread) become frozen, and my app becomes unresponsive. I've decided it may be a Garbage Collector-related issue, as the GC will freeze all managed threads temporarily. To verify that just managed threads are frozen, I spin up an unmanaged one that writes to a "heartbeat" file with a timestamp every second, and it is not affected (i.e. it still runs):

public delegate void ThreadProc();

[DllImport("UnmanagedTest.dll", EntryPoint = "MyUnmanagedFunction")]
public static extern void MyUnmanagedFunction();

[DllImport("kernel32")]
public static extern IntPtr CreateThread(
    IntPtr lpThreadAttributes,
    uint dwStackSize,
    IntPtr lpStartAddress,
    IntPtr lpParameter,
    uint dwCreationFlags,
    out uint dwThreadId);    

uint threadId;
ThreadProc proc = new ThreadProc(MyUnmanagedFunction);
IntPtr functionPointer = Marshal.GetFunctionPointerForDelegate(proc);
IntPtr threadHandle = CreateThread(IntPtr.Zero, 0, functionPointer, IntPtr.Zero, 0, out threadId);

My Question is: how can I simulate this situation, where all managed threads are suspended but unmanaged ones keep on spinning?

My first stab:

private void button1_Click(object sender, EventArgs e) {
    Thread t = new Thread(new ThreadStart(delegate {
        new Hanger();
        GC.Collect(2, GCCollectionMode.Forced);
    }));
    t.Start();
}
class Hanger{
    private int[] m_Integers = new int[10000000];
    public Hanger() { }
    ~Hanger() { Console.WriteLine("About to hang...");

    //This doesn't reproduce the desired behavior
    //while (true) ;

    //Neither does this
    //Thread.Sleep(System.Threading.Timeout.Infinite); 
    }
}

Thanks in advance!!

+1  A: 

Finalizers are executed concurrently with "normal" thread execution. We usually say that the GC runs the finalizers, but it would be truer that the GC detects which instances have finalizers which should be run, and stores them in a dedicated queue. A (hidden) thread fetches the instances from the queue and runs the finalizers. Such asynchronism is needed, e.g. because the finalizers may themselves allocate memory and potentially trigger a GC. There are other good reasons why finalizers are necessarily asynchronous.

Bottom-line is that you cannot alter, from ~Hanger(), what the VM does during a GC pause, because the thread which will actually run ~Hanger() is also paused at that time.

Thomas Pornin
A: 

I realize that this does not answer your question, but I suspect a deadlock in your code rather than a strange GC issue.

I would suggest to check your code for deadlocks, especially indirect cases like Control.Invoke calls when doing UI updates from background threads. Ensure that you are not holding a lock when calling an Invoke - this can cause unexpected deadlocks (as if any deadlock was expected :))

Marek
A: 

The issue DID in fact stem from the Garbage Collector. After many days of debugging and analyzing memory dumps with WinDbg, we realized that there was a deadlock situation, but induced by the GC collecting concurrently. Changing the GC to collect non-concurrently fixed our problem.

Pwninstein
A: 

Supporting Marek's answer, this seems much like a design problem with the model of concurrency you are using. Being a design problem, this is something you cannot effectively solve by testing.

My advice is to carefully consider the model of concurrency you are employing, and correct the design accordingly. Start by looking into the necessary conditions for a deadlock, e.g.:

  1. What mutual exclusions do you have?
  2. Which additional resources your processes (which already are using some resources) require?
  3. Which resources need explicit releasing be the process using them?

Taking these into account, if you have circular resource allocation structures you're looking into a probable deadlock situation.

Schedler