Jon Robertson
Jon Robertson

Reputation: 604

How to limit the memory that an application can allocate

I need a way to limit the amount of memory that a service may allocate in order to prevent the service from starving the system, similar to the way SQL Server allows you to set "Maximum server memory".

I know SetProcessWorkingSetSize doesn't do exactly what I want, but I'm trying to get it to behave the way that I believe it should. Regardless of the values that I use, my test app's working set is not limited. Further, if I call GetProcessWorkingSetSize immediately afterwards, the values returned are not what I previously specified. Here's the code used by my test app:

var
  MinWorkingSet: SIZE_T;
  MaxWorkingSet: SIZE_T;
begin
  if not SetProcessWorkingSetSize(GetCurrentProcess(), 20, 12800 ) then
    RaiseLastOSError();

  if GetProcessWorkingSetSize(GetCurrentProcess(), MinWorkingSet, MaxWorkingSet) then
    ShowMessage(Format('%d'#13#10'%d', [MinWorkingSet, MaxWorkingSet]));

No error occurs, but both the Min and Max values returned by GetProcessWorkingSetSize are 81,920.

I tried using SetProcessWorkingSetSizeEx using QUOTA_LIMITS_HARDWS_MAX_ENABLE ($00000004) in the Flags parameter. Unfortunately, SetProcessWorkingSetSizeEx fails with "Code 87. The parameter is incorrect" if I pass anything other than $00000000 in Flags.

I've also pursued using Job Objects to accomplish the same goal. I have memory limits working with Job Objects when launching a child process. However, I need the ability for a service to set its own memory limits rather than depending on a "launching" service to do it. So far, I haven't found a way for a single process to create a job object and then add itself to the job object. This always fails with Access Denied.

Any thoughts or suggestions?

Upvotes: 3

Views: 3263

Answers (2)

jb_dk
jb_dk

Reputation: 118

To get a handle to the object containing a process, first use IsProcessInJob() to check if there is a job to be found. Then use the semi-documented functions ZwOpenDirectoryObject(L"\BaseNamedObjects") and ZwQueryDirectoryObject() to get all the named system level objects and only look a the ones of type L "Job". Then try to open each such job object with JOB_OBJECT_QUERY access and use IsProcessInJob() to check if it is the right one. From there, reopen the job with actually needed access bits, recheck IsProcessInJob() (To survive race conditions) and use the handle as needed.

Note that some, but not all Job APIs accept a NULL handle as an alias of the current job, but not all the APIs you indicated you wanted to use.

Upvotes: 0

Ondrej Kelle
Ondrej Kelle

Reputation: 37211

The documentation of SetProcessWorkingSetSize function says:

dwMinimumWorkingSetSize [in]

...

This parameter must be greater than zero but less than or equal to the maximum working set size. The default size is 50 pages (for example, this is 204,800 bytes on systems with a 4K page size). If the value is greater than zero but less than 20 pages, the minimum value is set to 20 pages.

In case of a 4K page size, the imposed minimum value is 20 * 4096 = 81920 bytes which is the value you saw. The values are specified in bytes.

To actually limit the memory for your service process, I think it's possible to create a new job (CreateJobObject), set the memory limit (SetInformationJobObject) and assign your current process to the job (AssignProcessToJobObject) in the service's start up routine.

Unfortunately, on Windows before 8 and Server 2012, this won't work if the process already belongs to a job:

Windows 7, Windows Server 2008 R2, Windows XP with SP3, Windows Server 2008, Windows Vista and Windows Server 2003: The process must not already be assigned to a job; if it is, the function fails with ERROR_ACCESS_DENIED. This behavior changed starting in Windows 8 and Windows Server 2012.

If this is your case (ie. you get ERROR_ACCESS_DENIED on older Windows) check if the process is already assigned to a job (in which case, you're out of luck) but also make sure that it has the required access rights: PROCESS_SET_QUOTA and PROCESS_TERMINATE.

Upvotes: 3

Related Questions