Friday, January 8, 2010

Capping Memory for a Windows Process

I ran into an issue while running some heat transfer simulations in COMSOL. The software would start off using an acceptable amount of memory, so I would leave it overnight. Midway through the night, the solver entered a different stage, and the memory started sky-rocketing. This meant that windows started letting COMSOL use the swap as well as regular ram. Unfortunately there is no way in windows (that I know of) to force a program to either just use normal ram, or to cap the amount of memory it is allowed to use. I looked long and hard for such a method. It is an issue because a linear solver like COMSOL using swap for ram will tank a hard drive quickly (my hard drive actually died as a result).

Fortunately there is a way to solve this problem. We have to run the problem program inside a wrapper which assigns it a maximum amount of memory. The wrapper uses windows Job Objects to limit the amount of memory any given job or program can use. Here's my source code in C++:


// maxmem.cpp : Defines the entry point for the application.
//

#include

#include


int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
SIZE_T MaxMemory = 7864320000;

HANDLE job = CreateJobObject(0,"ComsolWrapper");

if (job == NULL) {
MessageBox(NULL,"Unable to create job object.",
"CreateJobObject Error",
MB_OK|MB_ICONEXCLAMATION);
return 1;
}

STARTUPINFO startupinfo;
PROCESS_INFORMATION processinformation;

memset(&startupinfo,0,sizeof(STARTUPINFO));
memset(&processinformation,0,sizeof(PROCESS_INFORMATION));

if (0 == CreateProcess("c:\\COMSOL35a\\bin\\comsol64.exe",0,0,0,false,
CREATE_SUSPENDED,0,0,
&startupinfo,&processinformation)) {
MessageBox(NULL,"Unable to open Comsol.",
"CreateProcess Error",MB_OK|MB_ICONEXCLAMATION);
return 1;
}

if (0 == AssignProcessToJobObject(job,processinformation.hProcess)) {
char *buffer = new char[256];

sprintf_s(buffer,256,
"Unable to assign Comsol process to Job Object, Error %d",
GetLastError());
MessageBox(NULL,buffer,"AssignProcessToJobObject Error",
MB_OK|MB_ICONEXCLAMATION);
}

JOBOBJECT_EXTENDED_LIMIT_INFORMATION joeli;

joeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_JOB_MEMORY;
joeli.BasicLimitInformation.MinimumWorkingSetSize = 0;
joeli.BasicLimitInformation.MaximumWorkingSetSize = 0;
joeli.BasicLimitInformation.ActiveProcessLimit = 0;
joeli.BasicLimitInformation.Affinity = 0;
joeli.BasicLimitInformation.PriorityClass = 0;
joeli.BasicLimitInformation.SchedulingClass = 0;
joeli.ProcessMemoryLimit = 0;
joeli.JobMemoryLimit = MaxMemory;
joeli.PeakJobMemoryUsed = 0;
joeli.PeakProcessMemoryUsed = 0;

if (0 == SetInformationJobObject(job,
JobObjectExtendedLimitInformation,
&joeli,
sizeof(joeli))) {
MessageBox(NULL,"Unable to set job object information.",
"SetInformationJobObject Error",
MB_OK|MB_ICONEXCLAMATION);
}

ResumeThread(processinformation.hThread);

WaitForSingleObject(processinformation.hProcess,INFINITE);

return 0;
}

I compiled this with Visual C++ Express Edition with no problems. If you need this feel free to use it. It could be written so that the name of the program and the memory limits are not hard coded, but I was just seeking a quick fix and I don't anticipate using this for any other applications at this point. Eventually I might write a command line intepreter for it so you can specify the target application and maximum memory, etc.