Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET thread in 'pre-emptive GC disabled' mode which blocking GC and potentially cause a deadlock

[Edit0] some good suggestion was provided by peoples for optimize instance creation(by using array buffer) and thus lower down the GC cycles, but please also explain why ONLY this thread marked as 'pre-emptive GC disabled', and is my case a deadlock?

Recently I'm doing some testing on our application (on Windows XP) which was written in .NET 3.5, I noticed in some cases the application performance is really bad, the memory shown in TaskManager is times than normal situation, but not hang. I did a Memory dump and simply put into Microsoft DebugDiag tool which is a good tool for people not very good at WinDbg.

I known there must have some memory leak issue in application, but the question here is about the GC.

There's one line in generated report:

Thread 6 triggered the garbage collection. The garbage collector thread wont start doing its work till the time the threads which have pre-emptive GC disabled have finished executing. The following threads have pre-emptive GC disabled: Thread 6

From the description, I understand it as a deadlock from the GC and 'pre-emptive GC disabled' threads, (but why not hang??) then I was wonder why that thread marked as 'pre-emptive GC disabled'.

Then I checked the Thread 6's call stack and find out the source code, it's a thread created explicitly by:

//Create the thread.
thread = new Thread(Execute) { Priority = ThreadPriority.Lowest };
thread.Start();

What this thread doing is thread-safely de-queue a log text line from a queue, and flush to a disk file:

// Wait for an event from the thread that en-queue
waitEvent.WaitOne();  
if (!disposed)
{
    // Clear the entry queue.  
    bool queueEmpty;
    do
    { 
        // Pick the next logEntry in the queue safely in a locked way.
        LogEntry logEntry = null;
        lock (entryQueueLock)
        {
            logEntry = entryQueue.Dequeue();
        }

        var text = logEntry.Text;
        byte[] textBytes = Encoding.Default.GetBytes(text);
        fileStream.Write(textBytes, 0, textBytes.Length);
        fileStream.Flush();            
     }
     while (!queueEmpty);
}

The call stack for Thread 6 stopped at this line, means this line kicked off the GC:

byte[] textBytes = Encoding.Default.GetBytes(text);

Then please help to explain why ONLY the Thread 6 (in nearly 50 threads) have this 'pre-emptive GC disabled', and how to avoid this situation: enter image description here

like image 495
Shawn Avatar asked Jan 20 '15 03:01

Shawn


People also ask

How does thread 2948 GC work?

Above, we see GC was triggered by thread 2948 and it waits for the CLR to suspend all managed threads. After that, the thread will collect the garbage and (as we will see in a moment) compact the heap. Note, heap compaction isn’t happening for every GC.

What is the workstation non-concurrent GC algorithm?

Here is a very rough algorithm for workstation non-concurrent GC: Application thread allocates an object and GC can’t fulfill the request. GC is started. CLR suspends all managed threads. CLR collects the garbage in the thread that triggered the GC. CLR resumes all application threads once GC is done.

What is concurrent mode in workstation GC?

Workstation GC: concurrent mode. In concurrent (or background) mode, the CLR creates a dedicated high-priority thread for Gen2 collection. In this case, the first phase of the garbage collection, mark phase, is happening in parallel with application threads.

Why does non-concurrent GC happen?

There are a few reasons for GC to happen: Generation 0 is full or Gen0 budget is reached, GC.Collect was called, or the system memory is low. We are only interested in the first option. Here is a very rough algorithm for workstation non-concurrent GC: Application thread allocates an object and GC can’t fulfill the request. GC is started.


1 Answers

This is not your problem specifically, but you can significantly reduce the amount of garbage that thread generates by following this advice concerning the Encoding class:

If your app must convert a large amount of data, it should reuse the output buffer. In this case, the GetBytes version that supports byte arrays is the best choice.

You can do that easily, like this:

/* helper extension method */
public static void Grow(ref byte[] buffer, int minSize)
{   if (minSize > buffer.Length) buffer = new byte[minSize];  }

/* outside the loop */
byte conversionBuffer[] = new byte[2048];
Encoding fileEncoding = Encoding.Default;

/* replace your GetBytes call with */
Grow(ref conversionBuffer, fileEncoding.GetMaxBytes(text));
int bytesUsed = fileEncoding.Get(text, 0, text.Count, conversionBuffer, 0);

/* and your Write call with */
fileStream.Write(conversionBuffer, 0, bytesUsed);

Also, I suggest you use a ConcurrentQueue instead of manually locking.

like image 169
Ben Voigt Avatar answered Nov 15 '22 05:11

Ben Voigt