Sophis Risque: how to manage/limit memory allocation?
Feb
6
Written by:
2/6/2014 6:59 PM
Even on x64 OS environment, it is not interesting to let the process using all the available memory. In some cases, like on CITRIX servers, the fact that one user consumes a lot of quantity will impact all users too. The goal of this post is to analyze all the possibilities that gives the MS SDK, in order to capture either bad memory allocation or to limit the process to a certain quantity of RAM.
Concerning the limitation to a certain quantity of RAM, it is possible to restrict by using the MS IO counters. The following code works fine in C++. It is possible to do same directly in C# by using PInvoke, or by encapsulating it in a native dll and to call it through a managed C++ class:
HANDLE
jobHandle = ::CreateJobObject(NULL,NULL);
if
(jobHandle != NULL)
{
size_t
max_memory =
size_t
(1024)*
size_t
(1024)* (
size_t
(1024)*
size_t
(4)) ;
//4.0Giga bytes
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobExtLimit;
memset
(&jobExtLimit,0,
sizeof
(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
jobExtLimit.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_MEMORY;
jobExtLimit.ProcessMemoryLimit = max_memory;
jobExtLimit.PeakProcessMemoryUsed = max_memory;
jobExtLimit.JobMemoryLimit = max_memory;
jobExtLimit.PeakJobMemoryUsed = max_memory;
if
(::SetInformationJobObject(jobHandle,JobObjectExtendedLimitInformation,&jobExtLimit,
sizeof
(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)))
{
if
(!::AssignProcessToJobObject(jobHandle,::GetCurrentProcess()))
{
DWORD
errorCode = ::GetLastError();
// TODO: some log
}
}
else
{
DWORD
errorCode = ::GetLastError();
// do some log again
....
Calling this code will restrict the process to 4GB. It means that if this limit is reached, the process will exit suddenly, without prevent the end user. The best is to use an environment variable that could be linked to some Sophis user too. Calling this method in UNIVERSAL_MAIN is something which makes sense. If batches have not be limited, methods on CSRApi that will give in which mode the toolkit is used can be called (CSRApi::IsInGUIMode).
Whatever, in some cases, Sophis hangs at the end, when the memory allocation fails. In these cases, in order to be sure to terminate and exit, it is possible in C++ to overload the the catch of the memory allocation exception by using for example this code in some toolkit dll:
#include
...
static
_PNH
_oldCallback = NULL;
int
HeapAllocFailure(
size_t
size)
{
__try
{
TerminateProcess(GetCurrentProcess(),-2);
}
__finally
{
}
return
NULL;
};
BOOL
APIENTRY DllMain(
HMODULE
hModule,
DWORD
ul_reason_for_call,
LPVOID
lpReserved
)
{
switch
(ul_reason_for_call)
{
case
DLL_PROCESS_ATTACH:
_oldCallback = _set_new_handler(HeapAllocFailure);
_set_new_mode(1);
break
;
Indicating 1 in the _set_new_mode method permits to override the exception in the new and malloc functions. It won't be called in case of memory allocation exception that occurs in C# code, if some assembly code makes memory allocation. The .Net engine has its own exception handling. Catching a problem of memomry allocation in C# is possible using the try/catch(OutOfMemoryException ) mechanism, but it should be placed in the C# code.