.NET 1.1 (and possibly 1.0 also) Threads leaking "Event" handles. [BUG??]

  • Thread starter Thread starter Rohit
  • Start date Start date
R

Rohit

Hi,

Threads in the .NET Framework 1.1 (and possibly in 1.0 also) leak "Event"
handles, by Event handles I mean Win32 Event handles which can be monitored
using the ProcessExplorer from www.sysinternals.com, or even simply look at
the Handle count in the good old windows TaskManager.
To demonstrate the problem, all I did was created a basic Win Forms
application and with Main implemented as:

[STAThread]
static void Main()
{
Application.Run(new Form1());
}

and Form's constructor as
public Form1()
{
while(true)
{
Thread thread = new Thread( new ThreadStart( ThreadProc) );
thread.Start();
Thread.Sleep(1000);
GC.Collect();
}
}
and the ThreadProc is implemented simply as
protected void ThreadProc()
{
Thread.Sleep(100);
return;
}

Now you let it run for a while and monitor the handle count using the
ProcessExplorer or TaskManager to can see that the thread count becomes more
or less constant after a bit (which means that an equilibrium is reached in
which the threads being created and thread which die is same), you will note
that the Handle count keeps on increasing. I have monitored upto 10000
handles after which I killed the application.
Now, this seems like a bug. Has someone got any explaination for this and
possibly a workaround. BTW, I also tried changing the attribute on Main from
STAThread to MTAThread with no change in behavior.
Any help/suggestions would be appreciated.

thanks,
Rohit
 
Please, try this from a console type application, and you will see that
there are no 'event' handle leaks related to the Thread start delegate.
If there's a leak is somewhere else in your code, could you post a complete
repro sample.
Willy.
 
Rohit said:
Hi,

Threads in the .NET Framework 1.1 (and possibly in 1.0 also) leak
"Event" handles, by Event handles I mean Win32 Event handles which
can be monitored using the ProcessExplorer from www.sysinternals.com,
or even simply look at the Handle count in the good old windows
TaskManager.
To demonstrate the problem, all I did was created a basic Win Forms
application and with Main implemented as:

[STAThread]
static void Main()
{
Application.Run(new Form1());
}

and Form's constructor as
public Form1()
{
while(true)
{
Thread thread = new Thread( new ThreadStart( ThreadProc) );
thread.Start();
Thread.Sleep(1000);
GC.Collect();

I cannot see a _real_ problem with any of your sample code when you add
explict calls to GC.Collect /and/ GC.WaitForPendingFinalizers. Thread has a
Protected Finalize and does not implement the IDisposable interface, so its
unmanaged handles (which seem to correspond to a Win32 Thread handle and a
handful of Event handles) don't get cleaned up until the GC get around to it.
And with an infinite loop creating threads, your test isn't going to do GC
until it determines there's a pressing need to do so.

So it's not really a bug, just an 'interesting' design decision to not provide
IDisposable for Thread.

BTW Anyone got a good way of forcing a running .NET process' GC to do a
collection?
 
BTW Anyone got a good way of forcing a running .NET process' GC to do a
collection?

GC.Collect() and GC.WaitForPendingFinalizers() will do it, but if you want
to reduce the working set you have to act on the Process class. Here's my
sample: http://blogs.geekdojo.net/richard/posts/338.aspx

Richard

--
C#, .NET and Complex Adaptive Systems:
http://blogs.geekdojo.net/Richard
Mark Hurd said:
Rohit said:
Hi,

Threads in the .NET Framework 1.1 (and possibly in 1.0 also) leak
"Event" handles, by Event handles I mean Win32 Event handles which
can be monitored using the ProcessExplorer from www.sysinternals.com,
or even simply look at the Handle count in the good old windows
TaskManager.
To demonstrate the problem, all I did was created a basic Win Forms
application and with Main implemented as:

[STAThread]
static void Main()
{
Application.Run(new Form1());
}

and Form's constructor as
public Form1()
{
while(true)
{
Thread thread = new Thread( new ThreadStart( ThreadProc) );
thread.Start();
Thread.Sleep(1000);
GC.Collect();

I cannot see a _real_ problem with any of your sample code when you add
explict calls to GC.Collect /and/ GC.WaitForPendingFinalizers. Thread has a
Protected Finalize and does not implement the IDisposable interface, so its
unmanaged handles (which seem to correspond to a Win32 Thread handle and a
handful of Event handles) don't get cleaned up until the GC get around to it.
And with an infinite loop creating threads, your test isn't going to do GC
until it determines there's a pressing need to do so.

So it's not really a bug, just an 'interesting' design decision to not provide
IDisposable for Thread.

BTW Anyone got a good way of forcing a running .NET process' GC to do a
collection?
 
Richard said:
GC.Collect() and GC.WaitForPendingFinalizers() will do it, but if you
want to reduce the working set you have to act on the Process class.
Here's my sample: http://blogs.geekdojo.net/richard/posts/338.aspx

Good stuff, but I'm refering to a running .NET sample like the one in this
thread. Is there a good way to get the GC to decide it should run even though
the process is busy.

I'm running the OP's sample code and it's got to over 21000 handles and I know
they're all ready for garbage collection. I assume I need to temporarily max
out one of the resources the GC monitors to determine when it should run
immediately, but I couldn't find a way that doesn't just cause random programs
to fail with out of memory errors...
 
Rohit said:
Hi Willy,
Thanks for your reply, I will try in a console application, however in the
meanwhile here is a small test app (basically its a win forms application in
C#) that will demonstrate the problem. I have attached a zip file containing
the whole project.
One quick note, this is a small app so I'm creating threads from inside the
Form's constructor so you will not see the window come up (since the
initialization of the main form never completes). However this has no effect
on the handle leak even if I were to start thread creation from outside.
thanks again,
Rohit

Ok I see, you do not call GC.Collect.
But what you observe is not a leak, in this small sample, the only objects
that get allocated on the GC heap are the Thread objects (~60 bytes/object),
Thread objects have an associated OS handle and a synchronization event
handle, so before the GC kicks in (for the second time) you will have
allocated thousands of them.
Now, in a real world application the GC will run more often and what you
observe now will not show up.

Willy.
 
Hi,
Thanks all for your input.
I had (before posting this question on this messageboard) played with
calling GC.Collect() and also GC.WaitForPendingFinalizers() explicitly, and
yes I do agree that in this small application the handles do get cleaned
out.
However, what actually prompted all this exercise is that in the product
that I'm working on we are seeing a similar handle leak. Now, thats a
remoting based application with a service running on the server side to
which client (one or more) connect and then do the stuff and then
disconnect. Now, in this application we are seeing a handle leak when we do
a regression testing. So, I started eliminating code to see what I find. So,
the smallest bit of code that still leaks handle goes like this:

A Singleton object is exposed remotly.
from another application, I make remoting calls, get this remote object, and
then call a method in this object. This method starts a thread, where in
ThreadProc is implemented like below and then returns.
protected void ThreadProc()
{
Thread.Sleep(1000);
GC.Collect();
return;
}
Even this code, despite my calling GC.Collect() is showing an increase in
handle count. And if I moniter using the new ProcessExplorer from
SysInternals.Com, which shows the number of GC calls (for Gen 0, 1 and 2),
or if I use PerfMon which also shows the GC calls, I see that the GC is
being called, but even then the handle count of thread and event objects is
growing.
Now, is it possible, that GC is ignoring (or in other words "incorrectly
handling") thread and event handles (i.e. objects of class Thread) and not
proper garbage collecting them, in a remoting scenario or in a scenario
where there are other objects to be collected it leaves behind the Thread
objects.
Any help in this regard would be greatly appreciated,
thanks,
Rohit
 
Mark,

If you run OP's console sample (including GC.Collect(), you will see that
the GC run's every second.

Willy.
 
@TK2MSFTNGP10.phx.gbl:

Here's what I ran:

using System;
using System.Threading;

class Class1
{
static void Main(string[] args)
{
while(true)
{
Thread thread = new Thread(new ThreadStart(ThreadProc));
thread.Start();
Thread.Sleep(1000);
GC.Collect();
}
}

static void ThreadProc()
{
Thread.Sleep(1500);
return;
}
}



No handle leak here.

Mark
 
Hi,
Thanks all for your input.
I had (before posting this question on this messageboard) played with
calling GC.Collect() and also GC.WaitForPendingFinalizers() explicitly, and
yes I do agree that in this small application the handles do get cleaned
out.
However, what actually prompted all this exercise is that in the product
that I'm working on we are seeing a similar handle leak. Now, thats a
remoting based application with a service running on the server side to
which client (one or more) connect and then do the stuff and then
disconnect. Now, in this application we are seeing a handle leak when we do
a regression testing. So, I started eliminating code to see what I find. So,
the smallest bit of code that still leaks handle goes like this:

A Singleton object is exposed remotly.
from another application, I make remoting calls, get this remote object, and
then call a method in this object. This method starts a thread, where in
ThreadProc is implemented like below and then returns.
protected void ThreadProc()
{
Thread.Sleep(1000);
GC.Collect();
return;
}
Even this code, despite my calling GC.Collect() is showing an increase in
handle count. And if I moniter using the new ProcessExplorer from
SysInternals.Com, which shows the number of GC calls (for Gen 0, 1 and 2),
or if I use PerfMon which also shows the GC calls, I see that the GC is
being called, but even then the handle count of thread and event objects is
growing.
Now, is it possible, that GC is ignoring (or in other words "incorrectly
handling") thread and event handles (i.e. objects of class Thread) and not
proper garbage collecting them, in a remoting scenario or in a scenario
where there are other objects to be collected it leaves behind the Thread
objects.
Any help in this regard would be greatly appreciated,
thanks,
Rohit
 
Mark,

If you run OP's console sample (including GC.Collect(), you will see
that the GC run's every second.

Yes. However, in the general case, say a test program or a threaded Mandelbrot
set generator, etc, can a system admin (not the programmer redesigning the
source) kick the GC into action externally?

In posing this question I was assuming the GC automatically checked other
Windows resources than memory (quite implicitly). Once I realised this is
probably not what an "Automatic memory management system" does, and I've
confirmed it:

Other than managed memory allocations, implementations of the
garbage collector do not maintain information about resources
held by an object, such as file handles or database connections.

(from About GC Class in the 1.1.1 docs.)

And yes, this is obvious in hindsight. I was just in a Windows frame of mind
when thinking about this.

So, I suppose it comes down to, "Is there an external API, or other 'hook',
(perhaps normally for debugging) that can initiate a GC?"
 
Mark said:
Rohit said:
Hi,

Threads in the .NET Framework 1.1 (and possibly in 1.0 also) leak
"Event" handles, by Event handles I mean Win32 Event handles which
can be monitored using the ProcessExplorer from
www.sysinternals.com, or even simply look at the Handle count in
the good old windows TaskManager.
To demonstrate the problem, all I did was created a basic Win Forms
application and with Main implemented as:

[STAThread]
static void Main()
{
Application.Run(new Form1());
}

and Form's constructor as
public Form1()
{
while(true)
{
Thread thread = new Thread( new ThreadStart( ThreadProc) );
thread.Start();
Thread.Sleep(1000);
GC.Collect();

I cannot see a _real_ problem with any of your sample code when you
add explict calls to GC.Collect /and/ GC.WaitForPendingFinalizers.
Thread has a Protected Finalize and does not implement the
IDisposable interface, so its unmanaged handles (which seem to
correspond to a Win32 Thread handle and a handful of Event handles)
don't get cleaned up until the GC get around to it. And with an
infinite loop creating threads, your test isn't going to do GC until
it determines there's a pressing need to do so.

So it's not really a bug, just an 'interesting' design decision to
not provide IDisposable for Thread.

I think Eric G has described the issue well here:

http://blogs.gotdotnet.com/EricGu/permalink.aspx/d1c4d43a-7f9e-4789-9422-6b1e3830988d

The difference being, the GDI wrappers can be Disposed, but Thread cannot.
 
Back
Top