From 0caf7472a129b1f82623f8348a66b834426d07a3 Mon Sep 17 00:00:00 2001 From: Omair Majid Date: Oct 28 2019 22:31:03 +0000 Subject: Add cgroupv2 support to .NET Core --- diff --git a/coreclr-cgroupv2-02.patch b/coreclr-cgroupv2-02.patch new file mode 100644 index 0000000..3097d86 --- /dev/null +++ b/coreclr-cgroupv2-02.patch @@ -0,0 +1,54 @@ +diff --git a/src/gc/unix/cgroup.cpp b/src/gc/unix/cgroup.cpp +index e5760bebd8..d9a4e2dedd 100644 +--- a/src/gc/unix/cgroup.cpp ++++ b/src/gc/unix/cgroup.cpp +@@ -31,6 +31,9 @@ Abstract: + #ifndef SIZE_T_MAX + #define SIZE_T_MAX (~(size_t)0) + #endif ++#ifndef _countof ++#define _countof(_array) (sizeof(_array) / sizeof(_array[0])) ++#endif // !_countof + + #define PROC_MOUNTINFO_FILENAME "/proc/self/mountinfo" + #define PROC_CGROUP_FILENAME "/proc/self/cgroup" +@@ -74,7 +77,7 @@ public: + if (s_memory_cgroup_path == nullptr) + return result; + +- mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files)); ++ mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, _countof(files)); + if (mem_limit_filename == nullptr) + return result; + +@@ -97,7 +100,7 @@ public: + if (s_memory_cgroup_path == nullptr) + return result; + +- mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files)); ++ mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, _countof(files)); + if (mem_usage_filename == nullptr) + return result; + +diff --git a/src/pal/src/misc/cgroup.cpp b/src/pal/src/misc/cgroup.cpp +index e32b74aabe..0b922123c8 100644 +--- a/src/pal/src/misc/cgroup.cpp ++++ b/src/pal/src/misc/cgroup.cpp +@@ -62,7 +61,7 @@ public: + if (s_memory_cgroup_path == nullptr) + return result; + +- mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files)); ++ mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, _countof(files)); + if (mem_limit_filename == nullptr) + return result; + +@@ -85,7 +84,7 @@ public: + if (s_memory_cgroup_path == nullptr) + return result; + +- mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files)); ++ mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, _countof(files)); + if (mem_usage_filename == nullptr) + return result; + diff --git a/coreclr-cgroupv2.patch b/coreclr-cgroupv2.patch new file mode 100644 index 0000000..2233808 --- /dev/null +++ b/coreclr-cgroupv2.patch @@ -0,0 +1,876 @@ +From 7b33e078b41819284175533215047359bb952b5d Mon Sep 17 00:00:00 2001 +From: Omair Majid +Date: Fri, 26 Jul 2019 17:12:48 -0400 +Subject: [PATCH] Add cgroup v2 support to CoreCLR + +Upstream cgroup v2 documentation is available at: +https://www.kernel.org/doc/Documentation/cgroup-v2.txt + +Some notable differences between cgroup v1 and v2, from a CoreCLR point +of view, include: + +- cgroup v2 has a single hierarchy, so we just look for a single "cgroup2" + entry in /proc/self/mountinfo (without looking for a subsystem match). + +- Since cgroup v2 has a single hierarchy, /proc/self/cgroup generally + has a single line "0::/path". There's no need to match subsystems or + hierarchy ids here. + +- "memory.limit_in_bytes" is now "memory.max". It can contain the + literal "max" to indicate no limit. + +- "memory.usage_in_bytes" is now "memory.current" + +- "cpu.cfs_quota_us" and "cpu.cfs_period_us" have been combined into a + single "cpu.max" file with the format "$MAX $PERIOD". The max value + can be a literal "max" to indicate a limit is not active. +--- + src/gc/unix/cgroup.cpp | 292 +++++++++++++++++++++++++++++------- + src/pal/src/misc/cgroup.cpp | 287 ++++++++++++++++++++++++++++------- + 2 files changed, 471 insertions(+), 108 deletions(-) + +diff --git a/src/gc/unix/cgroup.cpp b/src/gc/unix/cgroup.cpp +index 1e60898948..e5760bebd8 100644 +--- a/src/gc/unix/cgroup.cpp ++++ b/src/gc/unix/cgroup.cpp +@@ -35,20 +35,24 @@ Abstract: + #define PROC_MOUNTINFO_FILENAME "/proc/self/mountinfo" + #define PROC_CGROUP_FILENAME "/proc/self/cgroup" + #define PROC_STATM_FILENAME "/proc/self/statm" +-#define MEM_LIMIT_FILENAME "/memory.limit_in_bytes" +-#define MEM_USAGE_FILENAME "/memory.usage_in_bytes" +-#define CFS_QUOTA_FILENAME "/cpu.cfs_quota_us" +-#define CFS_PERIOD_FILENAME "/cpu.cfs_period_us" ++#define CGROUP1_MEMORY_LIMIT_FILENAME "/memory.limit_in_bytes" ++#define CGROUP2_MEMORY_LIMIT_FILENAME "/memory.max" ++#define CGROUP1_MEMORY_USAGE_FILENAME "/memory.usage_in_bytes" ++#define CGROUP2_MEMORY_USAGE_FILENAME "/memory.current" ++#define CGROUP1_CFS_QUOTA_FILENAME "/cpu.cfs_quota_us" ++#define CGROUP1_CFS_PERIOD_FILENAME "/cpu.cfs_period_us" ++#define CGROUP2_CPU_MAX_FILENAME "/cpu.max" ++ + + class CGroup + { +- static char* s_memory_cgroup_path; +- static char* s_cpu_cgroup_path; ++ static char *s_memory_cgroup_path; ++ static char *s_cpu_cgroup_path; + public: + static void Initialize() + { +- s_memory_cgroup_path = FindCgroupPath(&IsMemorySubsystem); +- s_cpu_cgroup_path = FindCgroupPath(&IsCpuSubsystem); ++ s_memory_cgroup_path = FindCGroupPath(&IsCGroup1MemorySubsystem); ++ s_cpu_cgroup_path = FindCGroupPath(&IsCGroup1CpuSubsystem); + } + + static void Cleanup() +@@ -59,20 +63,21 @@ public: + + static bool GetPhysicalMemoryLimit(size_t *val) + { ++ const char *files[] = { ++ CGROUP1_MEMORY_LIMIT_FILENAME, ++ CGROUP2_MEMORY_LIMIT_FILENAME ++ }; ++ + char *mem_limit_filename = nullptr; + bool result = false; + + if (s_memory_cgroup_path == nullptr) + return result; + +- size_t len = strlen(s_memory_cgroup_path); +- len += strlen(MEM_LIMIT_FILENAME); +- mem_limit_filename = (char*)malloc(len+1); ++ mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files)); + if (mem_limit_filename == nullptr) + return result; + +- strcpy(mem_limit_filename, s_memory_cgroup_path); +- strcat(mem_limit_filename, MEM_LIMIT_FILENAME); + result = ReadMemoryValueFromFile(mem_limit_filename, val); + free(mem_limit_filename); + return result; +@@ -80,6 +85,11 @@ public: + + static bool GetPhysicalMemoryUsage(size_t *val) + { ++ const char *files[] = { ++ CGROUP1_MEMORY_USAGE_FILENAME, ++ CGROUP2_MEMORY_USAGE_FILENAME ++ }; ++ + char *mem_usage_filename = nullptr; + bool result = false; + +@@ -87,14 +97,10 @@ public: + if (s_memory_cgroup_path == nullptr) + return result; + +- size_t len = strlen(s_memory_cgroup_path); +- len += strlen(MEM_USAGE_FILENAME); +- mem_usage_filename = (char*)malloc(len+1); ++ mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files)); + if (mem_usage_filename == nullptr) + return result; + +- strcpy(mem_usage_filename, s_memory_cgroup_path); +- strcat(mem_usage_filename, MEM_USAGE_FILENAME); + result = ReadMemoryValueFromFile(mem_usage_filename, val); + free(mem_usage_filename); + return result; +@@ -112,16 +118,149 @@ public: + } + + static bool GetCpuLimit(uint32_t *val) ++ { ++ if (GetCGroup1CpuLimit(val)) ++ { ++ return true; ++ } ++ ++ if (GetCGroup2CpuLimit(val)) ++ { ++ return true; ++ } ++ ++ return false; ++ } ++ ++private: ++ static bool IsCGroup1MemorySubsystem(const char *strTok){ ++ return strcmp("memory", strTok) == 0; ++ } ++ ++ static bool IsCGroup1CpuSubsystem(const char *strTok){ ++ return strcmp("cpu", strTok) == 0; ++ } ++ ++ static bool GetCGroup2CpuLimit(uint32_t *val) ++ { ++ char *line = nullptr; ++ char *filename = nullptr; ++ FILE *file = nullptr; ++ char *max_quota_string = nullptr; ++ char *period_string = nullptr; ++ ++ size_t lineLen = 0; ++ int sscanRet = 0; ++ long long max_quota = 0; ++ long long period = 0; ++ double cpu_count = 0; ++ ++ bool result = false; ++ ++ if (s_cpu_cgroup_path == nullptr) ++ { ++ return false; ++ } ++ ++ size_t filename_len = strlen(s_cpu_cgroup_path) + strlen(CGROUP2_CPU_MAX_FILENAME); ++ filename = (char*)malloc(filename_len + 1); ++ if (filename == nullptr) ++ { ++ goto done; ++ } ++ ++ strcpy(filename, s_cpu_cgroup_path); ++ strcat(filename, CGROUP2_CPU_MAX_FILENAME); ++ ++ file = fopen(filename, "r"); ++ if (file == nullptr) ++ { ++ goto done; ++ } ++ ++ if (getline(&line, &lineLen, file) == -1) ++ { ++ goto done; ++ } ++ ++ // The expected format is: ++ // $MAX $PERIOD ++ // Where "$MAX" may be the string literal "max" ++ ++ max_quota_string = (char*) malloc(lineLen + 1); ++ if (max_quota_string == nullptr) ++ { ++ goto done; ++ } ++ period_string = (char*) malloc(lineLen + 1); ++ if (period_string == nullptr) ++ { ++ goto done; ++ } ++ ++ sscanRet = sscanf(line, "%s %s", max_quota_string, period_string); ++ if (sscanRet != 2) ++ { ++ assert(!"Unable to parse " CGROUP2_CPU_MAX_FILENAME " file contents with sscanf."); ++ goto done; ++ } ++ ++ // "max" means no cpu limit ++ if (strncmp("max", max_quota_string, lineLen + 1) == 0) ++ { ++ goto done; ++ } ++ ++ errno = 0; ++ max_quota = atoll(max_quota_string); ++ if (errno != 0) ++ { ++ goto done; ++ } ++ ++ errno = 0; ++ period = atoll(period_string); ++ if (errno != 0) ++ { ++ goto done; ++ } ++ ++ // Cannot have less than 1 CPU ++ if (max_quota <= period) ++ { ++ result = true; ++ *val = 1; ++ goto done; ++ } ++ ++ // Calculate cpu count based on quota and round it up ++ cpu_count = (double) max_quota / period + 0.999999999; ++ *val = (cpu_count < UINT32_MAX) ? (uint32_t)cpu_count : UINT32_MAX; ++ ++ result = true; ++ ++ done: ++ if (file) ++ fclose(file); ++ free(filename); ++ free(line); ++ free(max_quota_string); ++ free(period_string); ++ ++ return result; ++ } ++ ++ static bool GetCGroup1CpuLimit(uint32_t *val) + { + long long quota; + long long period; + double cpu_count; + +- quota = ReadCpuCGroupValue(CFS_QUOTA_FILENAME); ++ quota = ReadCpuCGroupValue(CGROUP1_CFS_QUOTA_FILENAME); + if (quota <= 0) + return false; + +- period = ReadCpuCGroupValue(CFS_PERIOD_FILENAME); ++ period = ReadCpuCGroupValue(CGROUP1_CFS_PERIOD_FILENAME); + if (period <= 0) + return false; + +@@ -138,27 +277,19 @@ public: + + return true; + } +- +-private: +- static bool IsMemorySubsystem(const char *strTok){ +- return strcmp("memory", strTok) == 0; +- } + +- static bool IsCpuSubsystem(const char *strTok){ +- return strcmp("cpu", strTok) == 0; +- } +- +- static char* FindCgroupPath(bool (*is_subsystem)(const char *)){ ++ static char* FindCGroupPath(bool (*is_subsystem)(const char *)){ + char *cgroup_path = nullptr; + char *hierarchy_mount = nullptr; + char *hierarchy_root = nullptr; + char *cgroup_path_relative_to_mount = nullptr; ++ bool is_cgroupv2 = false; + +- FindHierarchyMount(is_subsystem, &hierarchy_mount, &hierarchy_root); ++ FindHierarchyMount(is_subsystem, &is_cgroupv2, &hierarchy_mount, &hierarchy_root); + if (hierarchy_mount == nullptr || hierarchy_root == nullptr) + goto done; + +- cgroup_path_relative_to_mount = FindCGroupPathForSubsystem(is_subsystem); ++ cgroup_path_relative_to_mount = FindCGroupPathForSubsystem(is_cgroupv2, is_subsystem); + if (cgroup_path_relative_to_mount == nullptr) + goto done; + +@@ -179,7 +310,7 @@ private: + return cgroup_path; + } + +- static void FindHierarchyMount(bool (*is_subsystem)(const char *), char** pmountpath, char** pmountroot) ++ static void FindHierarchyMount(bool (*is_subsystem)(const char *), bool* is_cgroupv2, char** pmountpath, char** pmountroot) + { + char *line = nullptr; + size_t lineLen = 0, maxLineLen = 0; +@@ -219,14 +350,18 @@ private: + assert(!"Failed to parse mount info file contents with sscanf."); + goto done; + } +- ++ + if (strncmp(filesystemType, "cgroup", 6) == 0) + { ++ if (strncmp(filesystemType, "cgroup2", 8) == 0) ++ { ++ *is_cgroupv2 = true; ++ } + char* context = nullptr; + char* strTok = strtok_r(options, ",", &context); + while (strTok != nullptr) + { +- if (is_subsystem(strTok)) ++ if (*is_cgroupv2 || is_subsystem(strTok)) + { + mountpath = (char*)malloc(lineLen+1); + if (mountpath == nullptr) +@@ -234,7 +369,7 @@ private: + mountroot = (char*)malloc(lineLen+1); + if (mountroot == nullptr) + goto done; +- ++ + sscanfRet = sscanf(line, + "%*s %*s %*s %s %s ", + mountroot, +@@ -261,8 +396,8 @@ private: + if (mountinfofile) + fclose(mountinfofile); + } +- +- static char* FindCGroupPathForSubsystem(bool (*is_subsystem)(const char *)) ++ ++ static char* FindCGroupPathForSubsystem(bool is_cgroupv2, bool (*is_subsystem)(const char *)) + { + char *line = nullptr; + size_t lineLen = 0; +@@ -274,7 +409,7 @@ private: + FILE *cgroupfile = fopen(PROC_CGROUP_FILENAME, "r"); + if (cgroupfile == nullptr) + goto done; +- ++ + while (!result && getline(&line, &lineLen, cgroupfile) != -1) + { + if (subsystem_list == nullptr || lineLen > maxLineLen) +@@ -289,28 +424,43 @@ private: + goto done; + maxLineLen = lineLen; + } +- +- // See man page of proc to get format for /proc/self/cgroup file +- int sscanfRet = sscanf(line, +- "%*[^:]:%[^:]:%s", +- subsystem_list, +- cgroup_path); +- if (sscanfRet != 2) ++ ++ if (is_cgroupv2) + { +- assert(!"Failed to parse cgroup info file contents with sscanf."); +- goto done; ++ // See https://www.kernel.org/doc/Documentation/cgroup-v2.txt ++ // Look for "0::/some/path" ++ int sscanfRet = sscanf(line, ++ "0::%s", ++ cgroup_path); ++ if (sscanfRet == 1) ++ { ++ result = true; ++ } + } +- +- char* context = nullptr; +- char* strTok = strtok_r(subsystem_list, ",", &context); +- while (strTok != nullptr) ++ else + { +- if (is_subsystem(strTok)) ++ // See man page of proc to get format for /proc/self/cgroup file ++ int sscanfRet = sscanf(line, ++ "%*[^:]:%[^:]:%s", ++ subsystem_list, ++ cgroup_path); ++ if (sscanfRet != 2) + { +- result = true; +- break; ++ assert(!"Failed to parse cgroup info file contents with sscanf."); ++ goto done; ++ } ++ ++ char* context = nullptr; ++ char* strTok = strtok_r(subsystem_list, ",", &context); ++ while (strTok != nullptr) ++ { ++ if (is_subsystem(strTok)) ++ { ++ result = true; ++ break; ++ } ++ strTok = strtok_r(nullptr, ",", &context); + } +- strTok = strtok_r(nullptr, ",", &context); + } + } + done: +@@ -325,6 +475,36 @@ private: + fclose(cgroupfile); + return cgroup_path; + } ++ ++ static char* SearchForFile(char* search_root, const char* possible_files[], size_t possible_files_len) ++ { ++ if (search_root == nullptr) ++ { ++ return nullptr; ++ } ++ ++ for (size_t i = 0; i < possible_files_len; i++) ++ { ++ const char* possible_file = possible_files[i]; ++ size_t len = strlen(search_root); ++ len += strlen(possible_file); ++ char* full_filename = (char*)malloc(len+1); ++ if (full_filename == nullptr) ++ { ++ return nullptr; ++ } ++ strcpy(full_filename, search_root); ++ strcat(full_filename, possible_file); ++ if (access(full_filename, R_OK) != -1) ++ { ++ return full_filename; ++ } ++ ++ free(full_filename); ++ } ++ ++ return nullptr; ++ } + + static bool ReadMemoryValueFromFile(const char* filename, size_t* val) + { +diff --git a/src/pal/src/misc/cgroup.cpp b/src/pal/src/misc/cgroup.cpp +index 3fc43a7b05..e32b74aabe 100644 +--- a/src/pal/src/misc/cgroup.cpp ++++ b/src/pal/src/misc/cgroup.cpp +@@ -23,10 +23,15 @@ + #define PROC_MOUNTINFO_FILENAME "/proc/self/mountinfo" + #define PROC_CGROUP_FILENAME "/proc/self/cgroup" + #define PROC_STATM_FILENAME "/proc/self/statm" +-#define MEM_LIMIT_FILENAME "/memory.limit_in_bytes" +-#define MEM_USAGE_FILENAME "/memory.usage_in_bytes" +-#define CFS_QUOTA_FILENAME "/cpu.cfs_quota_us" +-#define CFS_PERIOD_FILENAME "/cpu.cfs_period_us" ++#define CGROUP1_MEMORY_LIMIT_FILENAME "/memory.limit_in_bytes" ++#define CGROUP2_MEMORY_LIMIT_FILENAME "/memory.max" ++#define CGROUP1_MEMORY_USAGE_FILENAME "/memory.usage_in_bytes" ++#define CGROUP2_MEMORY_USAGE_FILENAME "/memory.current" ++#define CGROUP1_CFS_QUOTA_FILENAME "/cpu.cfs_quota_us" ++#define CGROUP1_CFS_PERIOD_FILENAME "/cpu.cfs_period_us" ++#define CGROUP2_CPU_MAX_FILENAME "/cpu.max" ++ ++ + class CGroup + { + static char *s_memory_cgroup_path; +@@ -34,8 +39,8 @@ + public: + static void Initialize() + { +- s_memory_cgroup_path = FindCgroupPath(&IsMemorySubsystem); +- s_cpu_cgroup_path = FindCgroupPath(&IsCpuSubsystem); ++ s_memory_cgroup_path = FindCGroupPath(&IsCGroup1MemorySubsystem); ++ s_cpu_cgroup_path = FindCGroupPath(&IsCGroup1CpuSubsystem); + } + + static void Cleanup() +@@ -43,23 +48,24 @@ + PAL_free(s_memory_cgroup_path); + PAL_free(s_cpu_cgroup_path); + } +- ++ + static bool GetPhysicalMemoryLimit(size_t *val) + { ++ const char *files[] = { ++ CGROUP1_MEMORY_LIMIT_FILENAME, ++ CGROUP2_MEMORY_LIMIT_FILENAME ++ }; ++ + char *mem_limit_filename = nullptr; + bool result = false; + + if (s_memory_cgroup_path == nullptr) + return result; + +- size_t len = strlen(s_memory_cgroup_path); +- len += strlen(MEM_LIMIT_FILENAME); +- mem_limit_filename = (char*)PAL_malloc(len+1); ++ mem_limit_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files)); + if (mem_limit_filename == nullptr) + return result; + +- strcpy_s(mem_limit_filename, len+1, s_memory_cgroup_path); +- strcat_s(mem_limit_filename, len+1, MEM_LIMIT_FILENAME); + result = ReadMemoryValueFromFile(mem_limit_filename, val); + PAL_free(mem_limit_filename); + return result; +@@ -67,36 +73,172 @@ + + static bool GetPhysicalMemoryUsage(size_t *val) + { ++ const char *files[] = { ++ CGROUP1_MEMORY_USAGE_FILENAME, ++ CGROUP2_MEMORY_USAGE_FILENAME ++ }; ++ + char *mem_usage_filename = nullptr; + bool result = false; + + if (s_memory_cgroup_path == nullptr) + return result; + +- size_t len = strlen(s_memory_cgroup_path); +- len += strlen(MEM_USAGE_FILENAME); +- mem_usage_filename = (char*)malloc(len+1); ++ mem_usage_filename = SearchForFile(s_memory_cgroup_path, files, sizeof(files)); + if (mem_usage_filename == nullptr) + return result; + +- strcpy(mem_usage_filename, s_memory_cgroup_path); +- strcat(mem_usage_filename, MEM_USAGE_FILENAME); + result = ReadMemoryValueFromFile(mem_usage_filename, val); +- free(mem_usage_filename); ++ PAL_free(mem_usage_filename); + return result; + } + + static bool GetCpuLimit(UINT *val) + { ++ if (GetCGroup1CpuLimit(val)) ++ { ++ return true; ++ } ++ ++ if (GetCGroup2CpuLimit(val)) ++ { ++ return true; ++ } ++ ++ return false; ++ } ++ ++private: ++ static bool IsCGroup1MemorySubsystem(const char *strTok){ ++ return strcmp("memory", strTok) == 0; ++ } ++ ++ static bool IsCGroup1CpuSubsystem(const char *strTok){ ++ return strcmp("cpu", strTok) == 0; ++ } ++ ++ static bool GetCGroup2CpuLimit(UINT *val) ++ { ++ char *line = nullptr; ++ char *filename = nullptr; ++ FILE *file = nullptr; ++ char *max_quota_string = nullptr; ++ char *period_string = nullptr; ++ ++ size_t lineLen = 0; ++ int sscanRet = 0; ++ long long max_quota = 0; ++ long long period = 0; ++ double cpu_count = 0; ++ ++ bool result = false; ++ ++ if (s_cpu_cgroup_path == nullptr) ++ { ++ return false; ++ } ++ ++ size_t filename_len = strlen(s_cpu_cgroup_path) + strlen(CGROUP2_CPU_MAX_FILENAME); ++ filename = (char*)PAL_malloc(filename_len + 1); ++ if (filename == nullptr) ++ { ++ goto done; ++ } ++ ++ strcpy_s(filename, filename_len + 1, s_cpu_cgroup_path); ++ strcat_s(filename, filename_len + 1, CGROUP2_CPU_MAX_FILENAME); ++ ++ file = fopen(filename, "r"); ++ if (file == nullptr) ++ { ++ goto done; ++ } ++ ++ if (getline(&line, &lineLen, file) == -1) ++ { ++ goto done; ++ } ++ ++ // The expected format is: ++ // $MAX $PERIOD ++ // Where "$MAX" may be the string literal "max" ++ ++ max_quota_string = (char*)PAL_malloc(lineLen + 1); ++ if (max_quota_string == nullptr) ++ { ++ goto done; ++ } ++ period_string = (char*)PAL_malloc(lineLen + 1); ++ if (period_string == nullptr) ++ { ++ goto done; ++ } ++ ++ sscanRet = sscanf_s(line, "%s %s", ++ max_quota_string, lineLen + 1, ++ period_string, lineLen + 1); ++ if (sscanRet != 2) ++ { ++ _ASSERTE(!"Unable to parse " CGROUP2_CPU_MAX_FILENAME " file contents with sscanf_s."); ++ goto done; ++ } ++ ++ // "max" means no cpu limit ++ if (strncmp("max", max_quota_string, lineLen + 1) == 0) ++ { ++ goto done; ++ } ++ ++ errno = 0; ++ max_quota = atoll(max_quota_string); ++ if (errno != 0) ++ { ++ goto done; ++ } ++ ++ errno = 0; ++ period = atoll(period_string); ++ if (errno != 0) ++ { ++ goto done; ++ } ++ ++ // Cannot have less than 1 CPU ++ if (max_quota <= period) ++ { ++ result = true; ++ *val = 1; ++ goto done; ++ } ++ ++ // Calculate cpu count based on quota and round it up ++ cpu_count = (double) max_quota / period + 0.999999999; ++ *val = (cpu_count < UINT_MAX) ? (UINT)cpu_count : UINT_MAX; ++ ++ result = true; ++ ++ done: ++ if (file) ++ fclose(file); ++ PAL_free(filename); ++ PAL_free(line); ++ PAL_free(max_quota_string); ++ PAL_free(period_string); ++ ++ return result; ++ } ++ ++ static bool GetCGroup1CpuLimit(UINT *val) ++ { + long long quota; + long long period; + double cpu_count; + +- quota = ReadCpuCGroupValue(CFS_QUOTA_FILENAME); ++ quota = ReadCpuCGroupValue(CGROUP1_CFS_QUOTA_FILENAME); + if (quota <= 0) + return false; + +- period = ReadCpuCGroupValue(CFS_PERIOD_FILENAME); ++ period = ReadCpuCGroupValue(CGROUP1_CFS_PERIOD_FILENAME); + if (period <= 0) + return false; + +@@ -114,27 +256,19 @@ + return true; + } + +-private: +- static bool IsMemorySubsystem(const char *strTok){ +- return strcmp("memory", strTok) == 0; +- } +- +- static bool IsCpuSubsystem(const char *strTok){ +- return strcmp("cpu", strTok) == 0; +- } +- +- static char* FindCgroupPath(bool (*is_subsystem)(const char *)){ ++ static char* FindCGroupPath(bool (*is_subsystem)(const char *)){ + char *cgroup_path = nullptr; + char *hierarchy_mount = nullptr; + char *hierarchy_root = nullptr; + char *cgroup_path_relative_to_mount = nullptr; ++ bool is_cgroupv2 = false; + size_t len; + +- FindHierarchyMount(is_subsystem, &hierarchy_mount, &hierarchy_root); ++ FindHierarchyMount(is_subsystem, &is_cgroupv2, &hierarchy_mount, &hierarchy_root); + if (hierarchy_mount == nullptr || hierarchy_root == nullptr) + goto done; + +- cgroup_path_relative_to_mount = FindCGroupPathForSubsystem(is_subsystem); ++ cgroup_path_relative_to_mount = FindCGroupPathForSubsystem(is_cgroupv2, is_subsystem); + if (cgroup_path_relative_to_mount == nullptr) + goto done; + +@@ -157,7 +291,7 @@ + return cgroup_path; + } + +- static void FindHierarchyMount(bool (*is_subsystem)(const char *), char** pmountpath, char** pmountroot) ++ static void FindHierarchyMount(bool (*is_subsystem)(const char *), bool* is_cgroupv2, char** pmountpath, char** pmountroot) + { + char *line = nullptr; + size_t lineLen = 0, maxLineLen = 0; +@@ -199,11 +333,15 @@ + + if (strncmp(filesystemType, "cgroup", 6) == 0) + { ++ if (strncmp(filesystemType, "cgroup2", 8) == 0) ++ { ++ *is_cgroupv2 = true; ++ } + char* context = nullptr; + char* strTok = strtok_s(options, ",", &context); + while (strTok != nullptr) + { +- if (is_subsystem(strTok)) ++ if (*is_cgroupv2 || is_subsystem(strTok)) + { + mountpath = (char*)PAL_malloc(lineLen+1); + if (mountpath == nullptr) +@@ -239,7 +377,7 @@ + fclose(mountinfofile); + } + +- static char* FindCGroupPathForSubsystem(bool (*is_subsystem)(const char *)) ++ static char* FindCGroupPathForSubsystem(bool is_cgroupv2, bool (*is_subsystem)(const char *)) + { + char *line = nullptr; + size_t lineLen = 0; +@@ -267,27 +405,42 @@ + maxLineLen = lineLen; + } + +- // See man page of proc to get format for /proc/self/cgroup file +- int sscanfRet = sscanf_s(line, +- "%*[^:]:%[^:]:%s", +- subsystem_list, lineLen+1, +- cgroup_path, lineLen+1); +- if (sscanfRet != 2) ++ if (is_cgroupv2) + { +- _ASSERTE(!"Failed to parse cgroup info file contents with sscanf_s."); +- goto done; ++ // See https://www.kernel.org/doc/Documentation/cgroup-v2.txt ++ // Look for "0::/some/path" ++ int sscanfRet = sscanf_s(line, ++ "0::%s", ++ cgroup_path, lineLen+1); ++ if (sscanfRet == 1) ++ { ++ result = true; ++ } + } +- +- char* context = nullptr; +- char* strTok = strtok_s(subsystem_list, ",", &context); +- while (strTok != nullptr) ++ else + { +- if (is_subsystem(strTok)) ++ // See man page of proc to get format for /proc/self/cgroup file ++ int sscanfRet = sscanf_s(line, ++ "%*[^:]:%[^:]:%s", ++ subsystem_list, lineLen+1, ++ cgroup_path, lineLen+1); ++ if (sscanfRet != 2) + { +- result = true; +- break; ++ _ASSERTE(!"Failed to parse cgroup info file contents with sscanf_s."); ++ goto done; ++ } ++ ++ char* context = nullptr; ++ char* strTok = strtok_s(subsystem_list, ",", &context); ++ while (strTok != nullptr) ++ { ++ if (is_subsystem(strTok)) ++ { ++ result = true; ++ break; ++ } ++ strTok = strtok_s(nullptr, ",", &context); + } +- strTok = strtok_s(nullptr, ",", &context); + } + } + done: +@@ -303,6 +456,36 @@ + return cgroup_path; + } + ++ static char* SearchForFile(char* search_root, const char* possible_files[], size_t possible_files_len) ++ { ++ if (search_root == nullptr) ++ { ++ return nullptr; ++ } ++ ++ for (size_t i = 0; i < possible_files_len; i++) ++ { ++ const char* possible_file = possible_files[i]; ++ size_t len = strlen(search_root); ++ len += strlen(possible_file); ++ char* full_filename = (char*)PAL_malloc(len+1); ++ if (full_filename == nullptr) ++ { ++ return nullptr; ++ } ++ strcpy_s(full_filename, len+1, search_root); ++ strcat_s(full_filename, len+1, possible_file); ++ if (access(full_filename, R_OK) != -1) ++ { ++ return full_filename; ++ } ++ ++ PAL_free(full_filename); ++ } ++ ++ return nullptr; ++ } ++ + static bool ReadMemoryValueFromFile(const char* filename, size_t* val) + { + return ::ReadMemoryValueFromFile(filename, val); +-- +2.23.0 diff --git a/corefx-39633-cgroupv2-mountpoints.patch b/corefx-39633-cgroupv2-mountpoints.patch new file mode 100644 index 0000000..34fbecb --- /dev/null +++ b/corefx-39633-cgroupv2-mountpoints.patch @@ -0,0 +1,46 @@ +From 1864630f762160e1cb439362cc0577471624192a Mon Sep 17 00:00:00 2001 +From: Omair Majid +Date: Fri, 19 Jul 2019 19:18:51 -0400 +Subject: [PATCH] Fix up cgroup2fs in Interop.MountPoints.FormatInfo + +`stat -fc %T /sys/fs/cgroup` calls this file system `cgroup2fs` + +Add the cgroup2fs file system magic number. Available from: + + - https://www.kernel.org/doc/Documentation/cgroup-v2.txt + - man 2 statfs + +Move cgroup2fs next to cgroupfs in the drive type list, since it is also +DriveType.Ram. +--- + .../Unix/System.Native/Interop.MountPoints.FormatInfo.cs | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs b/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs +index af38a2285ba2..4240bd4853ab 100644 +--- a/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs ++++ b/src/Common/src/Interop/Unix/System.Native/Interop.MountPoints.FormatInfo.cs +@@ -47,6 +47,7 @@ internal enum UnixFileSystemTypes : long + btrfs = 0x9123683E, + ceph = 0x00C36400, + cgroupfs = 0x0027E0EB, ++ cgroup2fs = 0x63677270, + cifs = 0xFF534D42, + coda = 0x73757245, + coherent = 0x012FF7B7, +@@ -231,7 +232,6 @@ private static DriveType GetDriveType(string fileSystemName) + case "bpf_fs": + case "btrfs": + case "btrfs_test": +- case "cgroup2fs": + case "coh": + case "daxfs": + case "drvfs": +@@ -384,6 +384,7 @@ private static DriveType GetDriveType(string fileSystemName) + case "binfmt_misc": + case "cgroup": + case "cgroupfs": ++ case "cgroup2fs": + case "configfs": + case "cramfs": + case "cramfs-wend": diff --git a/corefx-39686-cgroupv2-01.patch b/corefx-39686-cgroupv2-01.patch new file mode 100644 index 0000000..e7628e2 --- /dev/null +++ b/corefx-39686-cgroupv2-01.patch @@ -0,0 +1,391 @@ +From 2b2273ea4ea1c28472fa0d6ad2ffeb6374500550 Mon Sep 17 00:00:00 2001 +From: Omair Majid +Date: Wed, 23 Oct 2019 17:45:59 -0400 +Subject: [PATCH 1/2] Add cgroup v2 support to Interop.cgroups + +Fix up code to adjust cgroup v1 assumptions and check cgroup v2 paths, +locations and values. + +Continue using the older cgroup v1 terminology for APIs. +--- + .../Interop/Linux/cgroups/Interop.cgroups.cs | 116 ++++++++++++++---- + src/Common/tests/Common.Tests.csproj | 4 + + .../tests/Tests/Interop/cgroupsTests.cs | 107 ++++++++++++++++ + .../tests/DescriptionNameTests.cs | 2 +- + 4 files changed, 206 insertions(+), 23 deletions(-) + create mode 100644 src/Common/tests/Tests/Interop/cgroupsTests.cs + +diff --git a/src/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs b/src/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs +index 0ffd4d7b7c03..186fe0516c5b 100644 +--- a/src/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs ++++ b/src/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs +@@ -9,17 +9,22 @@ + + internal static partial class Interop + { ++ /// Provides access to some cgroup (v1 and v2) features + internal static partial class cgroups + { ++ // For cgroup v1, see https://www.kernel.org/doc/Documentation/cgroup-v1/ ++ // For cgroup v2, see https://www.kernel.org/doc/Documentation/cgroup-v2.txt ++ ++ /// The version of cgroup that's being used ++ internal enum CGroupVersion { None, CGroup1, CGroup2 }; ++ + /// Path to mountinfo file in procfs for the current process. + private const string ProcMountInfoFilePath = "/proc/self/mountinfo"; + /// Path to cgroup directory in procfs for the current process. + private const string ProcCGroupFilePath = "/proc/self/cgroup"; + +- /// Path to the found cgroup location, or null if it couldn't be found. +- internal static readonly string s_cgroupMemoryPath = FindCGroupPath("memory"); +- /// Path to the found cgroup memory limit_in_bytes path, or null if it couldn't be found. +- private static readonly string s_cgroupMemoryLimitPath = s_cgroupMemoryPath != null ? s_cgroupMemoryPath + "/memory.limit_in_bytes" : null; ++ /// Path to the found cgroup memory limit path, or null if it couldn't be found. ++ internal static readonly string s_cgroupMemoryLimitPath = FindCGroupMemoryLimitPath(); + + /// Tries to read the memory limit from the cgroup memory location. + /// The read limit, or 0 if it couldn't be read. +@@ -42,7 +47,7 @@ public static bool TryGetMemoryLimit(out ulong limit) + /// The path to the file to parse. + /// The parsed result, or 0 if it couldn't be parsed. + /// true if the value was read successfully; otherwise, false. +- private static bool TryReadMemoryValueFromFile(string path, out ulong result) ++ internal static bool TryReadMemoryValueFromFile(string path, out ulong result) + { + if (File.Exists(path)) + { +@@ -79,6 +84,11 @@ private static bool TryReadMemoryValueFromFile(string path, out ulong result) + result = checked(ulongValue * multiplier); + return true; + } ++ ++ // 'max' is also a possible valid value ++ // ++ // Treat this as 'no memory limit' and let the caller ++ // fallback to reading the real limit via other means + } + catch (Exception e) + { +@@ -90,12 +100,35 @@ private static bool TryReadMemoryValueFromFile(string path, out ulong result) + return false; + } + ++ /// Find the cgroup memory limit path. ++ /// The limit path if found; otherwise, null. ++ private static string FindCGroupMemoryLimitPath() ++ { ++ string cgroupMemoryPath = FindCGroupPath("memory", out CGroupVersion version); ++ if (cgroupMemoryPath != null) ++ { ++ if (version == CGroupVersion.CGroup1) ++ { ++ return cgroupMemoryPath + "/memory.limit_in_bytes"; ++ } ++ ++ if (version == CGroupVersion.CGroup2) ++ { ++ // 'memory.high' is a soft limit; the process may get throttled ++ // 'memory.max' is where OOM killer kicks in ++ return cgroupMemoryPath + "/memory.max"; ++ } ++ } ++ ++ return null; ++ } ++ + /// Find the cgroup path for the specified subsystem. + /// The subsystem, e.g. "memory". + /// The cgroup path if found; otherwise, null. +- private static string FindCGroupPath(string subsystem) ++ private static string FindCGroupPath(string subsystem, out CGroupVersion version) + { +- if (TryFindHierarchyMount(subsystem, out string hierarchyRoot, out string hierarchyMount) && ++ if (TryFindHierarchyMount(subsystem, out version, out string hierarchyRoot, out string hierarchyMount) && + TryFindCGroupPathForSubsystem(subsystem, out string cgroupPathRelativeToMount)) + { + // For a host cgroup, we need to append the relative path. +@@ -113,19 +146,24 @@ private static string FindCGroupPath(string subsystem) + /// The path of the directory in the filesystem which forms the root of this mount; null if not found. + /// The path of the mount point relative to the process's root directory; null if not found. + /// true if the mount was found; otherwise, null. +- private static bool TryFindHierarchyMount(string subsystem, out string root, out string path) ++ private static bool TryFindHierarchyMount(string subsystem, out CGroupVersion version, out string root, out string path) + { +- if (File.Exists(ProcMountInfoFilePath)) ++ return TryFindHierarchyMount(ProcMountInfoFilePath, subsystem, out version, out root, out path); ++ } ++ ++ internal static bool TryFindHierarchyMount(string mountInfoFilePath, string subsystem, out CGroupVersion version, out string root, out string path) ++ { ++ if (File.Exists(mountInfoFilePath)) + { + try + { +- using (var reader = new StreamReader(ProcMountInfoFilePath)) ++ using (var reader = new StreamReader(mountInfoFilePath)) + { + string line; + while ((line = reader.ReadLine()) != null) + { + // Look for an entry that has cgroup as the "filesystem type" +- // and that has options containing the specified subsystem. ++ // and, for cgroup1, that has options containing the specified subsystem + // See man page for /proc/[pid]/mountinfo for details, e.g.: + // (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) + // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue +@@ -148,17 +186,35 @@ private static bool TryFindHierarchyMount(string subsystem, out string root, out + continue; + } + +- if (postSeparatorlineParts[0] != "cgroup" || +- Array.IndexOf(postSeparatorlineParts[2].Split(','), subsystem) < 0) ++ bool validCGroup1Entry = ((postSeparatorlineParts[0] == "cgroup") && ++ (Array.IndexOf(postSeparatorlineParts[2].Split(','), subsystem) >= 0)); ++ bool validCGroup2Entry = postSeparatorlineParts[0] == "cgroup2"; ++ ++ if (!validCGroup1Entry && !validCGroup2Entry) + { + // Not the relevant entry. + continue; + } + +- // Found the relevant entry. Extract the mount root and path. ++ // Found the relevant entry. Extract the cgroup version, mount root and path. ++ switch (postSeparatorlineParts[0]) ++ { ++ case "cgroup": ++ version = CGroupVersion.CGroup1; ++ break; ++ case "cgroup2": ++ version = CGroupVersion.CGroup2; ++ break; ++ default: ++ version = CGroupVersion.None; ++ Debug.Fail($"invalid value for CGroupVersion \"{postSeparatorlineParts[0]}\""); ++ break; ++ } ++ + string[] lineParts = line.Substring(0, endOfOptionalFields).Split(' '); + root = lineParts[3]; + path = lineParts[4]; ++ + return true; + } + } +@@ -169,6 +225,7 @@ private static bool TryFindHierarchyMount(string subsystem, out string root, out + } + } + ++ version = CGroupVersion.None; + root = null; + path = null; + return false; +@@ -180,27 +237,42 @@ private static bool TryFindHierarchyMount(string subsystem, out string root, out + /// + private static bool TryFindCGroupPathForSubsystem(string subsystem, out string path) + { +- if (File.Exists(ProcCGroupFilePath)) ++ return TryFindCGroupPathForSubsystem(ProcCGroupFilePath, subsystem, out path); ++ } ++ ++ internal static bool TryFindCGroupPathForSubsystem(string procCGroupFilePath, string subsystem, out string path) ++ { ++ if (File.Exists(procCGroupFilePath)) + { + try + { +- using (var reader = new StreamReader(ProcCGroupFilePath)) ++ using (var reader = new StreamReader(procCGroupFilePath)) + { + string line; + while ((line = reader.ReadLine()) != null) + { +- // Find the first entry that has the subsystem listed in its controller +- // list. See man page for cgroups for /proc/[pid]/cgroups format, e.g: +- // hierarchy-ID:controller-list:cgroup-path +- // 5:cpuacct,cpu,cpuset:/daemons +- + string[] lineParts = line.Split(':'); ++ + if (lineParts.Length != 3) + { + // Malformed line. + continue; + } + ++ // cgroup v2: Find the first entry that matches the cgroup v2 hierarchy: ++ // 0::$PATH ++ ++ if ((lineParts[0] == "0") && (string.Empty == lineParts[1])) ++ { ++ path = lineParts[2]; ++ return true; ++ } ++ ++ // cgroup v1: Find the first entry that has the subsystem listed in its controller ++ // list. See man page for cgroups for /proc/[pid]/cgroups format, e.g: ++ // hierarchy-ID:controller-list:cgroup-path ++ // 5:cpuacct,cpu,cpuset:/daemons ++ + if (Array.IndexOf(lineParts[1].Split(','), subsystem) < 0) + { + // Not the relevant entry. +@@ -214,7 +286,7 @@ private static bool TryFindCGroupPathForSubsystem(string subsystem, out string p + } + catch (Exception e) + { +- Debug.Fail($"Failed to read or parse \"{ProcMountInfoFilePath}\": {e}"); ++ Debug.Fail($"Failed to read or parse \"{procCGroupFilePath}\": {e}"); + } + } + +diff --git a/src/Common/tests/Common.Tests.csproj b/src/Common/tests/Common.Tests.csproj +index a189d856348b..979c8dd7fbe6 100644 +--- a/src/Common/tests/Common.Tests.csproj ++++ b/src/Common/tests/Common.Tests.csproj +@@ -12,6 +12,9 @@ + + Common\System\Security\Cryptography\ByteUtils.cs + ++ ++ Common\Interop\Linux\cgroups\Interop.cgroups.cs ++ + + Common\Interop\Linux\procfs\Interop.ProcFsStat.cs + +@@ -69,6 +72,7 @@ + + Common\CoreLib\System\PasteArguments.cs + ++ + + + +diff --git a/src/Common/tests/Tests/Interop/cgroupsTests.cs b/src/Common/tests/Tests/Interop/cgroupsTests.cs +new file mode 100644 +index 000000000000..f16d9242879c +--- /dev/null ++++ b/src/Common/tests/Tests/Interop/cgroupsTests.cs +@@ -0,0 +1,107 @@ ++// Licensed to the .NET Foundation under one or more agreements. ++// The .NET Foundation licenses this file to you under the MIT license. ++// See the LICENSE file in the project root for more information. ++ ++using System; ++using System.IO; ++using System.Text; ++using Xunit; ++ ++namespace Common.Tests ++{ ++ public class cgroupsTests ++ { ++ [Theory] ++ [InlineData(true, "0", 0)] ++ [InlineData(false, "max", 0)] ++ [InlineData(true, "1k", 1024)] ++ [InlineData(true, "1K", 1024)] ++ public static void ValidateTryReadMemoryValue(bool expectedResult, string valueText, ulong expectedValue) ++ { ++ string path = Path.GetTempFileName(); ++ try ++ { ++ File.WriteAllText(path, valueText); ++ ++ bool result = Interop.cgroups.TryReadMemoryValueFromFile(path, out ulong val); ++ ++ Assert.Equal(expectedResult, result); ++ if (result) ++ { ++ Assert.Equal(expectedValue, val); ++ } ++ } ++ finally ++ { ++ File.Delete(path); ++ } ++ } ++ ++ [Theory] ++ [InlineData(false, "0 0 0:0 / /foo ignore ignore - overlay overlay ignore", "ignore", 0, "/", "/")] ++ [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup2 cgroup2 ignore", "ignore", 2, "/", "/foo")] ++ [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup2 cgroup2 ignore", "memory", 2, "/", "/foo")] ++ [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup2 cgroup2 ignore", "cpu", 2, "/", "/foo")] ++ [InlineData(true, "0 0 0:0 / /foo ignore - cgroup2 cgroup2 ignore", "cpu", 2, "/", "/foo")] ++ [InlineData(true, "0 0 0:0 / /foo ignore ignore ignore - cgroup2 cgroup2 ignore", "cpu", 2, "/", "/foo")] ++ [InlineData(true, "0 0 0:0 / /foo-with-dashes ignore ignore - cgroup2 cgroup2 ignore", "ignore", 2, "/", "/foo-with-dashes")] ++ [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup cgroup memory", "memory", 1, "/", "/foo")] ++ [InlineData(true, "0 0 0:0 / /foo-with-dashes ignore ignore - cgroup cgroup memory", "memory", 1, "/", "/foo-with-dashes")] ++ [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup cgroup cpu,memory", "memory", 1, "/", "/foo")] ++ [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup cgroup memory,cpu", "memory", 1, "/", "/foo")] ++ [InlineData(false, "0 0 0:0 / /foo ignore ignore - cgroup cgroup cpu", "memory", 0, "/", "/foo")] ++ public static void ParseValidateMountInfo(bool found, string procSelfMountInfoText, string subsystem, int expectedVersion, string expectedRoot, string expectedMount) ++ { ++ string path = Path.GetTempFileName(); ++ try ++ { ++ File.WriteAllText(path, procSelfMountInfoText); ++ ++ bool result = Interop.cgroups.TryFindHierarchyMount(path, subsystem, out Interop.cgroups.CGroupVersion version, out string root, out string mount); ++ ++ Assert.Equal(found, result); ++ if (found) ++ { ++ Assert.Equal(expectedVersion, (int)version); ++ Assert.Equal(expectedRoot, root); ++ Assert.Equal(expectedMount, mount); ++ } ++ } ++ finally ++ { ++ File.Delete(path); ++ } ++ } ++ ++ [Theory] ++ [InlineData(true, "0::/foo", "ignore", "/foo")] ++ [InlineData(true, "0::/bar", "ignore", "/bar")] ++ [InlineData(true, "0::frob", "ignore", "frob")] ++ [InlineData(false, "1::frob", "ignore", "ignore")] ++ [InlineData(true, "1:foo:bar", "foo", "bar")] ++ [InlineData(true, "2:foo:bar", "foo", "bar")] ++ [InlineData(false, "2:foo:bar", "bar", "ignore")] ++ [InlineData(true, "1:foo:bar\n2:eggs:spam", "foo", "bar")] ++ [InlineData(true, "1:foo:bar\n2:eggs:spam", "eggs", "spam")] ++ public static void ParseValidateProcCGroup(bool found, string procSelfCgroupText, string subsystem, string expectedMountPath) ++ { ++ string path = Path.GetTempFileName(); ++ try ++ { ++ File.WriteAllText(path, procSelfCgroupText); ++ ++ bool result = Interop.cgroups.TryFindCGroupPathForSubsystem(path, subsystem, out string mountPath); ++ ++ Assert.Equal(found, result); ++ if (found) ++ { ++ Assert.Equal(expectedMountPath, mountPath); ++ } ++ } ++ finally ++ { ++ File.Delete(path); ++ } ++ } ++ } ++} +diff --git a/src/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs b/src/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs +index 910af2fd82b4..73f692898dbc 100644 +--- a/src/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs ++++ b/src/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs +@@ -40,7 +40,7 @@ public void DumpRuntimeInformationToConsole() + + Console.WriteLine($"### CURRENT DIRECTORY: {Environment.CurrentDirectory}"); + +- string cgroupsLocation = Interop.cgroups.s_cgroupMemoryPath; ++ string cgroupsLocation = Interop.cgroups.s_cgroupMemoryLimitPath; + if (cgroupsLocation != null) + { + Console.WriteLine($"### CGROUPS MEMORY: {cgroupsLocation}"); + diff --git a/corefx-39686-cgroupv2-02.patch b/corefx-39686-cgroupv2-02.patch new file mode 100644 index 0000000..88dcd99 --- /dev/null +++ b/corefx-39686-cgroupv2-02.patch @@ -0,0 +1,129 @@ +From 9a8c5e4014ffca8aff70808cc0e50a403d38c292 Mon Sep 17 00:00:00 2001 +From: Stephen Toub +Date: Wed, 23 Oct 2019 20:35:49 -0400 +Subject: [PATCH 2/2] Clean up new tests + +--- + .../tests/Tests/Interop/cgroupsTests.cs | 79 ++++++------------- + 1 file changed, 25 insertions(+), 54 deletions(-) + +diff --git a/src/Common/tests/Tests/Interop/cgroupsTests.cs b/src/Common/tests/Tests/Interop/cgroupsTests.cs +index f16d9242879c..fc6ab5c9753c 100644 +--- a/src/Common/tests/Tests/Interop/cgroupsTests.cs ++++ b/src/Common/tests/Tests/Interop/cgroupsTests.cs +@@ -2,38 +2,27 @@ + // The .NET Foundation licenses this file to you under the MIT license. + // See the LICENSE file in the project root for more information. + +-using System; + using System.IO; +-using System.Text; + using Xunit; + + namespace Common.Tests + { +- public class cgroupsTests ++ public class cgroupsTests : FileCleanupTestBase + { + [Theory] +- [InlineData(true, "0", 0)] +- [InlineData(false, "max", 0)] +- [InlineData(true, "1k", 1024)] +- [InlineData(true, "1K", 1024)] +- public static void ValidateTryReadMemoryValue(bool expectedResult, string valueText, ulong expectedValue) ++ [InlineData(true, "0", 0)] ++ [InlineData(false, "max", 0)] ++ [InlineData(true, "1k", 1024)] ++ [InlineData(true, "1K", 1024)] ++ public void ValidateTryReadMemoryValue(bool expectedResult, string valueText, ulong expectedValue) + { +- string path = Path.GetTempFileName(); +- try +- { +- File.WriteAllText(path, valueText); +- +- bool result = Interop.cgroups.TryReadMemoryValueFromFile(path, out ulong val); ++ string path = GetTestFilePath(); ++ File.WriteAllText(path, valueText); + +- Assert.Equal(expectedResult, result); +- if (result) +- { +- Assert.Equal(expectedValue, val); +- } +- } +- finally ++ Assert.Equal(expectedResult, Interop.cgroups.TryReadMemoryValueFromFile(path, out ulong val)); ++ if (expectedResult) + { +- File.Delete(path); ++ Assert.Equal(expectedValue, val); + } + } + +@@ -50,26 +39,17 @@ public static void ValidateTryReadMemoryValue(bool expectedResult, string valueT + [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup cgroup cpu,memory", "memory", 1, "/", "/foo")] + [InlineData(true, "0 0 0:0 / /foo ignore ignore - cgroup cgroup memory,cpu", "memory", 1, "/", "/foo")] + [InlineData(false, "0 0 0:0 / /foo ignore ignore - cgroup cgroup cpu", "memory", 0, "/", "/foo")] +- public static void ParseValidateMountInfo(bool found, string procSelfMountInfoText, string subsystem, int expectedVersion, string expectedRoot, string expectedMount) ++ public void ParseValidateMountInfo(bool expectedFound, string procSelfMountInfoText, string subsystem, int expectedVersion, string expectedRoot, string expectedMount) + { +- string path = Path.GetTempFileName(); +- try +- { +- File.WriteAllText(path, procSelfMountInfoText); +- +- bool result = Interop.cgroups.TryFindHierarchyMount(path, subsystem, out Interop.cgroups.CGroupVersion version, out string root, out string mount); ++ string path = GetTestFilePath(); ++ File.WriteAllText(path, procSelfMountInfoText); + +- Assert.Equal(found, result); +- if (found) +- { +- Assert.Equal(expectedVersion, (int)version); +- Assert.Equal(expectedRoot, root); +- Assert.Equal(expectedMount, mount); +- } +- } +- finally ++ Assert.Equal(expectedFound, Interop.cgroups.TryFindHierarchyMount(path, subsystem, out Interop.cgroups.CGroupVersion version, out string root, out string mount)); ++ if (expectedFound) + { +- File.Delete(path); ++ Assert.Equal(expectedVersion, (int)version); ++ Assert.Equal(expectedRoot, root); ++ Assert.Equal(expectedMount, mount); + } + } + +@@ -83,24 +63,15 @@ public static void ParseValidateMountInfo(bool found, string procSelfMountInfoTe + [InlineData(false, "2:foo:bar", "bar", "ignore")] + [InlineData(true, "1:foo:bar\n2:eggs:spam", "foo", "bar")] + [InlineData(true, "1:foo:bar\n2:eggs:spam", "eggs", "spam")] +- public static void ParseValidateProcCGroup(bool found, string procSelfCgroupText, string subsystem, string expectedMountPath) ++ public void ParseValidateProcCGroup(bool expectedFound, string procSelfCgroupText, string subsystem, string expectedMountPath) + { +- string path = Path.GetTempFileName(); +- try +- { +- File.WriteAllText(path, procSelfCgroupText); ++ string path = GetTestFilePath(); ++ File.WriteAllText(path, procSelfCgroupText); + +- bool result = Interop.cgroups.TryFindCGroupPathForSubsystem(path, subsystem, out string mountPath); +- +- Assert.Equal(found, result); +- if (found) +- { +- Assert.Equal(expectedMountPath, mountPath); +- } +- } +- finally ++ Assert.Equal(expectedFound, Interop.cgroups.TryFindCGroupPathForSubsystem(path, subsystem, out string mountPath)); ++ if (expectedFound) + { +- File.Delete(path); ++ Assert.Equal(expectedMountPath, mountPath); + } + } + } diff --git a/dotnet3.0.spec b/dotnet3.0.spec index 6f5cc10..623479a 100644 --- a/dotnet3.0.spec +++ b/dotnet3.0.spec @@ -52,7 +52,7 @@ Name: dotnet3.0 Version: %{sdk_rpm_version} -Release: 4%{?dist} +Release: 5%{?dist} Summary: .NET Core CLI tools and runtime License: MIT and ASL 2.0 and BSD URL: https://github.com/dotnet/ @@ -64,9 +64,14 @@ Source1: check-debug-symbols.py Source2: dotnet.sh.in Patch100: corefx-optflags-support.patch +Patch101: corefx-39686-cgroupv2-01.patch +Patch102: corefx-39686-cgroupv2-02.patch +Patch103: corefx-39633-cgroupv2-mountpoints.patch Patch200: coreclr-27048-sysctl-deprecation.patch Patch201: coreclr-hardening-flags.patch +Patch202: coreclr-cgroupv2.patch +Patch203: coreclr-cgroupv2-02.patch Patch300: core-setup-do-not-strip.patch Patch301: core-setup-hardening-flags.patch @@ -94,13 +99,11 @@ BuildRequires: libicu-devel %if ! %{use_bundled_libunwind} BuildRequires: libunwind-devel %endif -BuildRequires: libuuid-devel BuildRequires: lldb-devel BuildRequires: llvm BuildRequires: lttng-ust-devel BuildRequires: openssl-devel BuildRequires: python3 -BuildRequires: strace BuildRequires: tar BuildRequires: zlib-devel @@ -302,11 +305,16 @@ sed -i 's|skiptests|skiptests ignorewarnings|' repos/coreclr.proj pushd src/corefx.* %patch100 -p1 +%patch101 -p1 +%patch102 -p1 +%patch103 -p1 popd pushd src/coreclr.* %patch200 -p1 %patch201 -p1 +%patch202 -p1 +%patch203 -p1 popd pushd src/core-setup.* @@ -438,6 +446,9 @@ echo "Testing build results for debug symbols..." %dir %{_libdir}/dotnet/packs %changelog +* Thu Oct 24 2019 Omair Majid - 3.0.100-5 +- Add cgroupv2 support to .NET Core + * Wed Oct 16 2019 Omair Majid - 3.0.100-4 - Include fix from coreclr for building on Fedora 32