Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

memory limit in containers on linux #10064

Merged
merged 1 commit into from
Mar 23, 2017
Merged

memory limit in containers on linux #10064

merged 1 commit into from
Mar 23, 2017

Conversation

rahku
Copy link

@rahku rahku commented Mar 9, 2017

This adds implementation in pal to get memory limit from cgroups. Memory limit values are written in file <path_to_memory_cgroup>/memory.limit_in_bytes and <path_to_memory_cgroup>/memory.memsw.limit_int_bytes. path_to_memory_cgroup is <path_to_memory_hierarchy>/<cgroup_name>. cgroup_name can be found in file /proc/self/cgroup. <path_to_memory_hierarchy>is normally set to /sys/fs/cgroup/memory. However this is not the standard. It can be changed to any mount point. Standard way of getting that value is from /proc/self/mountinfo.

Note: CGroup implementation is not available on OSX. Docker does not run natively on OSX. It runs under a light-weigth hypervisor which runs a linux distro and you add container on top of it. So the current implementation so work just fine in this scenario.

@rahku
Copy link
Author

rahku commented Mar 9, 2017

I have tested this on ubuntu. I am still doing some additional testing.

@rahku
Copy link
Author

rahku commented Mar 9, 2017

PTAL @janvorli @gkhanna79 @Maoni0

@@ -78,6 +78,22 @@ static uint8_t g_helperPage[OS_PAGE_SIZE] __attribute__((aligned(OS_PAGE_SIZE)))
// Mutex to make the FlushProcessWriteBuffersMutex thread safe
static pthread_mutex_t g_flushProcessWriteBuffersMutex;

static size_t g_RestrictedPhysicalMemoryLimit = (size_t)MAX_PTR;

static size_t GetRestrictedPhysicalMemoryLimit()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is for pure Unix only and so you cannot use any PAL functions here. It is used when the GC is compiled as a standalone library.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, no contracts and no windowsizms should be here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would imply code duplication..is that ok

@janvorli
Copy link
Member

janvorli commented Mar 9, 2017

Yes, code duplication is ok here.


class CGroup
{
char _memory_cgroup_path[MAX_PATH];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For native code, the convention is to use m_ instead of _ as a member prefix.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please use StackString or its specialization PathStackString instead of MAX_PATH sized arrays everywhere where the size is not known? We have introduced StackString to deal with variable sized paths on Unix.
The code will also become cleaner since you would not need to care about passing the MAX_PATH limit everywhere.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I have just realized that the StackString is not available out of PAL for the Unix only implementation, so please ignore this.

#include "pal/palinternal.h"
#include <sys/resource.h>

#define MAX_SIZE_T (~(SIZE_T)0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use SIZE_T_MAX which is available in PAL

strcpy(_memory_cgroup_path, memory_cgroup_path);
}

const char* path()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For functions, we use pascal case naming convention in PAL.

return _memory_cgroup_path;
}

bool physicalMemoryLimit(size_t &val)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please name this methods GetPhysicalMemoryLimit and GetSwapMemoryLimit? That would make the purpose clearer for readers of the code. The swapMemoryLimit is especially confusing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the convention in PAL is not to use references for output parameters, but rather use pointers.

"- %s %*s %s",
filesystemType, (unsigned)_countof(filesystemType),
options, (unsigned)_countof(options));
if (sscanfRet != 2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please unify the spacing style between the if / while and the following (? Sometimes you have space there and sometimes not. I prefer the style with space, but I'll leave it up to you.

char* separatorChar = strchr(line, '-');

// See man page of proc to get format for /proc/self/mountinfo file
int sscanfRet = sscanf_s(separatorChar,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the sscanf_s and other _s functions are not available for plain Unix. You'll need to use the plain ones or maybe better add defines that define sscanf_s as sscanf etc so that the rest of the code is the same as for the PAL version.

int __cdecl main(int argc,char *argv[])
{

char * AnExampleString = "this is the string";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These three variables doesn't seem to be used. Can you remove them please?

FILE* file = fopen("/sys/fs/cgroup/memory/mygroup/memory.limit_in_bytes", "r");
if(file != NULL)
{
if(mem_limit != 4194304)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am surprised the test can run at all with just 4MB limit. But I assume you have verified that.

Read memory limits for the current process
--*/

#define MAX_SIZE_T (~(SIZE_T)0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that these defines can be in the cgroup.cpp since they are not part of the public interface of the CGroup.

size_t restricted_limit = GetRestrictedPhysicalMemoryLimit();
if (restricted_limit != 0)
if (restricted_limit != 0 && restricted_limit != (size_t)MAX_PTR)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the added check needed? It looks like the GetRestrictedPhysicalMemoryLimit should return zero if there is no limit to be on par with the Windows version.

uint64_t restricted_limit = GetRestrictedPhysicalMemoryLimit();
if (restricted_limit != 0)
if (restricted_limit != 0 && restricted_limit != (size_t)MAX_PTR)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same question here

switch(*endptr)
{
case 'g':
case 'G': multiplier = 1024;
Copy link
Member

@gkhanna79 gkhanna79 Mar 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there no breaks expected here? Certain compilers can complain about it - it will be good to logically segregate them with a break.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no breaks are not needed it needs to fall though

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code as it is would not work. You are multiplying bool (result) by 1024. I guess the intention was to use multiplier = multiplier * 1024 in there.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. thanks for catching it. I did test it before and then changed variable names but missed here.

@janvorli
Copy link
Member

janvorli commented Mar 9, 2017

@rahku have you tried to build it on Linux with the buildstandalonegc option? That compiles all the pure unix parts that are not built otherwise.

@rahku
Copy link
Author

rahku commented Mar 9, 2017

No i did not know that ...will do

@swgillespie
Copy link

There's also a CI job that runs buildstandalonegc: https://ci.dot.net/job/dotnet_coreclr/job/master/job/x64_checked_ubuntu_standalone_gc/ . I think it can be triggered on PRs with test Ubuntu Checked standalone_gc.

options, (unsigned)_countof(options));
if (sscanfRet != 2)
{
_ASSERTE(!"Failed to parse mount info file contents with sscanf_s.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We prefer vanilla C/C++ for the standalone GC PAL: _ASSERTE -> assert

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i m in process of making these changes.

@rahku
Copy link
Author

rahku commented Mar 10, 2017

@dotnet-bot test Ubuntu Checked standalone_gc

@rahku
Copy link
Author

rahku commented Mar 10, 2017

@dotnet-bot test Ubuntu x64 Checked standalone_gc

@rahku
Copy link
Author

rahku commented Mar 10, 2017

PTAL i have incorprorated all review comments

uint64_t restricted_limit = GetRestrictedPhysicalMemoryLimit();
if (restricted_limit != 0)
{
size_t workingSetSize;
BOOL status = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

false [](start = 22, length = 5)

nit... FALSE instead of false.


class CGroup
{
char m_memory_cgroup_path[MAX_PATH];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hungarian notation is Windowsism... . Change it to just _memory_cgroup_path ?

bool ReadMemoryValueFromFile(const char* filename, size_t* val);
};

size_t GetRestrictedPhysicalMemoryLimit();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would look better to have these methods as static public methods on CGroup, and have all instance methods in CGroup to be private; or move class CGroup to the .cpp file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it is in the .cpp file in the CoreCLR PAL, I think it should be in the .cpp file here as well.

@rahku rahku force-pushed the cgroup branch 2 times, most recently from 49ff670 to 11d92a7 Compare March 10, 2017 07:36

class CGroup
{
char _memory_cgroup_path[MAX_PATH];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkotas I have asked @rahku to use m_ convention instead, but I could see you have asked him to change it back However, we use m_ in all coreclr PAL and also in CoreRT native code, so I'd really prefer staying in line with that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @jkotas was asking to change in this file which is for standalone gc & linux specific and so should not have any windowism?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not have a strong opinion. I will leave it up to you what you like best.

size_t num = 0, l, multiplier;
FILE* file = NULL;

if (val == NULL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - you have this check only here, but not in the other copy of the function.

if (g_RestrictedPhysicalMemoryLimit != 0)
return g_RestrictedPhysicalMemoryLimit;

size_t memory_limit = GetRestrictedPhysicalMemoryLimit();
Copy link
Member

@jkotas jkotas Mar 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't there an infinite recursion (GetRestrictedPhysicalMemoryLimit calling GetRestrictedPhysicalMemoryLimit)? I think this can be moved to GCToOSInterface::GetPhysicalMemoryLimit.


size_t GetRestrictedPhysicalMemoryLimit()
{
CGroup cgroup;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This does not seem to be using 4 space indentation

// See man page of proc to get format for /proc/self/cgroup file
int sscanfRet = sscanf(line,
"%*[^:]:%[^:]:%s",
subsystem_list,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this hardened against buffer overruns? What guarantees that the result will fit into subsystem_list?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no secure versions of these in linux. Are you suggesting to not to use these non-secure crt functions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are using these non-secure crt functions, it is up to you to guarantee no buffer overruns.

@rahku
Copy link
Author

rahku commented Mar 11, 2017

unix specific cgroup.cpp is now hardened against buffer overruns. @jkotas does that look fine now?

@rahku
Copy link
Author

rahku commented Mar 15, 2017

I have tested this on fedora & ubuntu and also done some end-to-end testing and results look good. One of the difference that I found wrt to windows is that on Linux when we go out of memory the process is killed by OS instead of allocation failure. This is with the default setting of the machine...there might be settings to change it.

@rahku
Copy link
Author

rahku commented Mar 15, 2017

@janvorli I also now do not look at file memory.memsw.limit_in_bytes. This file gives the swap space limit. Swap space limits the virtual address of the process and does not impact real memory that can be used by the process (in our case I am assuming only a single process to be run in a cgroup). But for gc we need real memory pressure and not virtual address space pressure.

@rahku
Copy link
Author

rahku commented Mar 15, 2017

@dotnet-bot test Windows_NT x64 Debug Build and Test

@rahku
Copy link
Author

rahku commented Mar 16, 2017

@dotnet-bot test OSX x64 Checked Build and Test


bool GetPhysicalMemoryLimit(size_t *val)
{
char mem_limit_filename[MAX_PATH];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some time ago, we have made a sweeping change over the CoreCLR native code including PAL to get rid of fixed size buffers for file paths unless the path is guaranteed to never exceed that limit. So introducing such a fixed size buffer here makes me nervous. I would prefer using a dynamically allocated buffer at this place. You can use the length of the line from the mount info file that contains the path as an memory size estimate - we would just waste a little memory that's not needed for the path. The FindMemoryHierarchyMount can then return nullptr or the allocated buffer.
I think that similar thing can be done at other places where you use MAX_PATH.

if (file != NULL && getline(&line, &linelen, file) != -1)
{
int x = sscanf_s(line, "%*s %llu %*s %*s %*s %*s %*s", val);
if(x==1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - could you please add spaces around the == to unify it with the rest of the code?

done:
free(filesystemType);
free(options);
if (line)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - this "if" is not needed, free can accept nullptr just fine.

free(cgroup_path);
cgroup_path = nullptr;
}
if (line)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - this "if" is not needed, free can accept nullptr just fine.

done:
if (file)
fclose(file);
if (line)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - this "if" is not needed, free can accept nullptr just fine.


// Ensure that limit is not greater than real memory size
size_t pages = (size_t) sysconf(_SC_PHYS_PAGES);
if (pages != -1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

size_t is not a signed type, it seems you can use long here.

{
bool result = false;
size_t linelen;
char* line = NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please unify on using nullptr everywhere instead of NULL at some places and nullptr elsewhere?


if (file)
fclose(file);
if (line)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nit - this "if" is not needed, free can accept nullptr just fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same case in the PAL version of this code for all the occurences.

{
available = sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE);
uint64_t used = total - available;
available = total > used ? total-used : 0;
load = (uint32_t)((used * 100) / total);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could overflow on 32 bit hardware, you'll need to keep the used type to be uint64_t.


// Ensure that limit is not greater than real memory size
size_t pages = (size_t) sysconf(_SC_PHYS_PAGES);
if (pages != -1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

size_t is an unsigned type

Copy link
Member

@janvorli janvorli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you!

@rahku
Copy link
Author

rahku commented Mar 22, 2017

@dotnet-bot test Tizen armel Cross Debug Build
@dotnet-bot test Tizen armel Cross Release Build
@dotnet-bot test Ubuntu arm Cross Release Build
@dotnet-bot test Ubuntu16.04 arm Cross Debug Build

@rahku
Copy link
Author

rahku commented Mar 22, 2017

@hseok-oh you recently changed the arm test runs. Everything was passing earlier for me before your changes. I do not have a device/emulator to debug this. Could you take a look at failures or point to instructions on how to use arm emulator.

@janvorli
Copy link
Member

@rahku - The problem is some buffer overrun. I can see in the log the following which indicates that:
*** stack smashing detected ***

@hseok-oh
Copy link

hseok-oh commented Mar 23, 2017

@rahku In fact, our CI ARM emulator didn't test run since testrun script changed. That could check build success only. So we removed old ARM emulator build test and reconfigured build and test based on docker.

@rahku rahku merged commit b511095 into dotnet:master Mar 23, 2017
@rahku rahku deleted the cgroup branch March 29, 2017 19:02
@karelz karelz modified the milestone: 2.0.0 Aug 28, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants