Click here to Skip to main content
Click here to Skip to main content
Go to top

Set process memory limit with Process Governor

, 21 Nov 2013
Rate this:
Please Sign up or sign in to vote.
I wrote this tool to test my .NET applications (including web applications) for memory leaks.

Today I would like to introduce you to Process Governor – a new tool I added to my .NET diagnostics toolkit. This application allows you to set a limit on a memory committed by a process. On Windows committed memory is actually all private memory that the process uses. I wrote this tool to test my .NET applications (including web applications) for memory leaks. With it I can check if under heavy load they won’t throw OutOfMemoryException.

Usage scenarios

The command line syntax is really simple, you just specify a memory limit using the -maxmem switch and then provide your application path and its arguments. You can either start a new process:

procgov.exe -maxmem 30M .\example\TestMemory.exe

or attach to an already running one:

procgov  -maxmem 30M -p 6040

The -p parameter corresponds to the process identifier (PID). The difference between those two is that when you start a process it has the memory constraint already set – so we will receive an OutOfMemory at the moment it occurs. When you attach to a process that already exceeded the memory limit it will be immediately terminated so no OutOfMemory will be thrown.

How it works?

Process Governor uses a system job object to apply constraints to a process. Unfortunately there is no managed API to work with jobs so I needed to import some functions from pinvoke.net. Let’s quickly analyze the source code of this application. Before we can assign a process to a job (and apply constraints on it) we need to obtain its handle. When attaching to a process we will use the OpenProcess function:

hProcess = CheckResult(ApiMethods.OpenProcess(ProcessAccessFlags.AllAccess, false, pid));

If we start a new process we will use the CreateProcess function:

PROCESS_INFORMATION pi;
STARTUPINFO si = new STARTUPINFO();

CheckResult(ApiMethods.CreateProcess(null, String.Join(" ", procargs), IntPtr.Zero, IntPtr.Zero, false,
            CreateProcessFlags.CREATE_SUSPENDED | CreateProcessFlags.CREATE_NEW_CONSOLE,
           IntPtr.Zero, null, ref si, out pi));

I haven’t used the Process.Create managed method as it does not allow to start a process in a suspended state.

After obtaining a process handle we can start working with a job object. First, we need to create it:

var securityAttributes = new SECURITY_ATTRIBUTES();
securityAttributes.nLength = Marshal.SizeOf(securityAttributes);

hJob = CheckResult(ApiMethods.CreateJobObject(ref securityAttributes, "procgov-" + Guid.NewGuid()));

Then we will create a completion port for listening to job events:

// create completion port
hIOCP = CheckResult(ApiMethods.CreateIoCompletionPort(ApiMethods.INVALID_HANDLE_VALUE, IntPtr.Zero, IntPtr.Zero, 1));
var assocInfo = new JOBOBJECT_ASSOCIATE_COMPLETION_PORT {
    CompletionKey = IntPtr.Zero,
    CompletionPort = hIOCP
};
uint size = (uint)Marshal.SizeOf(assocInfo);
CheckResult(ApiMethods.SetInformationJobObject(hJob, JOBOBJECTINFOCLASS.AssociateCompletionPortInformation,
        ref assocInfo, size));

// start listening thread
listener = new Thread(CompletionPortListener);
listener.Start(hIOCP);

The CompletionPortListener method has a simple switch and transforms message identifier to some meaningful messages. That’s a pity that there is no managed API for I/O completion port. They seem to be one of the greatest mechanism in Windows to asynchronously communicate between threads. The name is a bit misleading as it suggests that there may be used only for I/O operations – the truth is that you may use them to any type of asynchronous communication between threads (the job notifier is one of examples). In our case as we have only one process run in a job so one listening thread is enough.

Finally we apply a process memory limit (if provided) and assign the newly created process to our job:

if (maxmem > 0.0f) {
    // configure constraints
    var limitInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION {
        BasicLimitInformation = new JOBOBJECT_BASIC_LIMIT_INFORMATION {
            LimitFlags = JobInformationLimitFlags.JOB_OBJECT_LIMIT_PROCESS_MEMORY
                        | JobInformationLimitFlags.JOB_OBJECT_LIMIT_BREAKAWAY_OK
                        | JobInformationLimitFlags.JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK
        },
        ProcessMemoryLimit = (UIntPtr)maxmem
    };
    size = (uint)Marshal.SizeOf(limitInfo);
    CheckResult(ApiMethods.SetInformationJobObject(hJob, JOBOBJECTINFOCLASS.ExtendedLimitInformation,
            ref limitInfo, size));
}

// assign a process to a job to apply constraints
CheckResult(ApiMethods.AssignProcessToJobObject(hJob, hProcess));

Remember that we created a process in a suspended mode so if the PID was not provided we need to resume the main process thread:

// resume process main thread (if it was started by us)
if (pid == 0) {
    CheckResult(ApiMethods.ResumeThread(pi.hThread));
    // and we can close the thread handle
    CloseHandle(pi.hThread);
}

The process should be now running and we can wait for it’s completion:

if (ApiMethods.WaitForSingleObject(hProcess, ApiMethods.INFINITE) == 0xFFFFFFFF) {
    throw new Win32Exception();
}

The source code and the application can be downloaded from the .NET diagnostics toolkit codeplex site. I encourage you to have a deeper look at what the job object provides. In my application I’m setting limit on a process committed memory, but you may as well restrict process CPU usage and its working set size. Have fun with jobs and fight with memory leaks :)  

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sebastian Solnica
Software Developer (Senior)
Poland Poland
Interested in tracing, debugging and performance tuning of the .NET applications (especially ASP.NET).
 
If you find this article interesting, maybe you would like to pay me a visit: http://lowleveldesign.wordpress.com? Smile | :)

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 21 Nov 2013
Article Copyright 2013 by Sebastian Solnica
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid