Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handles leak (Event type) in .NET application

I have a Windows Forms application written in .NET 4.0. Recently, while execution some tests, I noticed that there is some problem with handles. Table below shows the results:

enter image description here

As you can see the, only handle type which is increasing is Event.

So my question is: Is it possible that the described problem is caused by a Windows Forms application? I mean, I do not synchronize threads using AutoResetEvent or ManualResetEvent. I do use threads, but what can be seen from the table above the number of thread handles seems to be ok. So, I assume that they are well managed by CLR?

Can it be caused by any third party components I am also using in my app?

If sth is unclear I will try to answer your questions. Thanks for help!

like image 609
rwasik Avatar asked Feb 16 '14 18:02

rwasik


People also ask

What is a handle leak in software?

A handle leak is a program bug that occurs when a computer program, such as Outlook, requests a connection to a resource on the computer, but does not release the connection when it has completed its task. These are not infections and cannot be fixed by Webroot.

What are memory leaks C#?

A memory leak may happen when your app references objects that it no longer needs to perform the desired task. Referencing said objects makes the garbage collector to be unable to reclaim the memory used, often resulting in performance degradation and potentially end up throwing an OutOfMemoryException.


2 Answers

Events are the main source of memory leaks in .Net, and AutoResetEvent and ManualResetEvent are very badly named. They are not the cause.

When you see something like this:

myForm.OnClicked += Form_ClickHandler

That is the type of event this is talking about. When you register an event handler, the event source (like OnClicked) keeps a reference to the handler. If you create and register new handlers you MUST unregister the event (like myForm.OnClicked -= Form_ClickHandler) otherwise your memory use will keep growing.

For more info:

  • Why and How to avoid Event Handler memory leaks?
  • C# Events Memory Leak
like image 172
Iain Ballard Avatar answered Oct 16 '22 04:10

Iain Ballard


This answer is a bit late, but I just ran across the question while investigating a very similar issue in some of my code and found the answer by placing a break point at the syscall in the disassembly of CreateEvent. Hopefully other people will find this answer useful, even if it is too late for your specific use case.

The answer is that .NET creates Event kernel objects for various threading primitives when there is contention. Notably, I have made a test application that can show they are created when using the "lock" statement, though, presumably, any of the Slim threading primitives will perform similar lazy creation.

It is important to note that the handles are NOT leaked, though an increasing number may indicate a leak elsewhere in your code. The handles will be released when the garbage collector collects the object that created them (eg, the object provided in the lock statement).

I have pasted my test code below which will showcase the leak on a small scale (around 100 leaked Event handles on my test machine - your mileage may vary).

A few specific points of interest:

  • Once the list is cleared and the GC.Collect() is run, any created handles will be cleaned up.

  • Setting ThreadCount to 1 will prevent any Event handles from being created.

  • Similarly, commenting out the lock statement will cause no handles to be created.

  • Removing the ThreadCount from the calculation of index (line 72) will drastically reduce contention and thus prevent nearly all the handles from being created.

  • No matter how long you let it run for, it will never create more than 200 handles (.NET seems to create 2 per object for some reason).

using System.Collections.Generic;
using System.Threading;

namespace Dummy.Net
{
    public static class Program
    {
        private static readonly int ObjectCount = 100;
        private static readonly int ThreadCount = System.Environment.ProcessorCount - 1;

        private static readonly List<object> _objects = new List<object>(ObjectCount);
        private static readonly List<Thread> _threads = new List<Thread>(ThreadCount);

        private static int _currentIndex = 0;
        private static volatile bool _finished = false;
        private static readonly ManualResetEventSlim _ready = new ManualResetEventSlim(false, 1024);

        public static void Main(string[] args)
        {
            for (int i = 0; i < ObjectCount; ++i)
            {
                _objects.Add(new object());
            }

            for (int i = 0; i < ThreadCount; ++i)
            {
                var thread = new Thread(ThreadMain);
                thread.Name = $"Thread {i}";
                thread.Start();
                _threads.Add(thread);
            }

            System.Console.WriteLine("Ready.");

            Thread.Sleep(10000);

            _ready.Set();
            System.Console.WriteLine("Started.");

            Thread.Sleep(10000);

            _finished = true;

            foreach (var thread in _threads)
            {
                thread.Join();
            }

            System.Console.WriteLine("Finished.");

            Thread.Sleep(3000);

            System.Console.WriteLine("Collecting.");

            _objects.Clear();
            System.GC.Collect();

            Thread.Sleep(3000);

            System.Console.WriteLine("Collected.");

            Thread.Sleep(3000);
        }

        private static void ThreadMain()
        {
            _ready.Wait();

            while (!_finished)
            {
                int rawIndex = Interlocked.Increment(ref _currentIndex);
                int index = (rawIndex / ThreadCount) % ObjectCount;
                bool sleep = rawIndex % ThreadCount == 0;

                if (!sleep)
                {
                    Thread.Sleep(10);
                }

                object obj = _objects[index];
                lock (obj)
                {
                    if (sleep)
                    {
                        Thread.Sleep(250);
                    }
                }
            }
        }
    } 
}
like image 23
DragonFireCK Avatar answered Oct 16 '22 05:10

DragonFireCK