Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Process.GetProcessesByName(String, String) Memory Leak

I have a piece of code that gets a list of processes on a remote computer using the static method Process.GetProcessesByName(String, String), this runs on a lot of computers (a few thousands) and I've noticed it's a cause of a major memory leak.

I ran ANTS memory profiler which told me that most of my memory is taken by strings, strings containing strage values like "% Idle Time", "Processor Information", and "Cache Faults/sec". I've recognized those strings as probably being a part of Performance Counters in the program, the problem is I don't have any performance counters in the program.

Digging deeper found out those strings are held in hashtables that are held by PerformanceCounterLib which are held by ANOTHER hashtable that is stored inside an internal static member of the PerformanceCounterLib class (which in itself is internal).

Digging even deeper into the rabbit hole, I've found out that Process.GetProcesesByName uses PerformanceCounterLib to get the process list running on a distant computer and that for each remote computer another PerformanceCounterLib instance is created and referenced in the static internal variable of PerformanceCounterLib. Each of those instances hold that hashtable of strings that I found out is clogging my memory (each of them is between 300-700 kb, meaning it's clogging up my Large Object Heap).

I did not find a way to delete those unused PerformanceCounterLib instances, they are all internal and the user has no access to them.

How can I fix my memory problem? This is REALLY bad, my program hits 5GB (my server's limit) within 24 hours.

EDIT: added a piece of code (not tested) that should reproduce the problem. For clarification:

/// computerNames is a list of computers that you have access to
public List<string> GetProcessesOnAllComputers(List<string> computerNames)
{
    var result = new List<string>();
    foreach(string compName in computernames)
    {
        Process[] processes = Process.GetProcesses(compName); // Happens with every     method that gets processes on a remote computer
        string processString = processes.Aggregate(new StringBuilder(), (sb,s) => sb.Append(';').Append(s), sb => sb.ToString());
        result.Add(processString);
        foreach (var p in processes)
        {
            p.Close();
            p.Dispose();
        }
        processes = null;
    }
}
like image 884
Ziv Avatar asked Oct 26 '12 09:10

Ziv


1 Answers

You can call PerformanceCounter.CloseSharedResources.

Internally, this calls PerformanceCounterLib.CloseAllLibraries, which does what it sounds like.

I'd advise making sure that you call this at a time when no calls to GetProcessesByName are in progress, since it looks like there may be some race conditions inside PerformanceCounterLib that you don't want to provoke.

i.e. there's a shared variable called libraryTable that is checked once then assumed to continue to be valid in one method, and yet might be cleared by CloseAllLibraries at any time - so its decidedly not thread safe.

like image 182
Damien_The_Unbeliever Avatar answered Nov 15 '22 10:11

Damien_The_Unbeliever