diff --git a/src/libraries/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs b/src/libraries/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs index 973d4c63573e70..513ed5306fa5a7 100644 --- a/src/libraries/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs +++ b/src/libraries/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs @@ -116,16 +116,24 @@ internal static bool TryGetMemoryLimitV1(out ulong limit) /// The read limit, or 0 if it couldn't be read. /// true if the limit was read successfully; otherwise, false. internal static bool TryGetMemoryLimitV2(out ulong limit) + { + return TryGetMemoryLimitV2(s_cgroupMemoryPath, s_cgroupMemoryHierarchyMountPath, out limit); + } + + /// Tries to read the memory limit from a cgroup v2 path hierarchy. + /// The current cgroup memory path. + /// The cgroup memory hierarchy mount path. + /// The read limit, or 0 if it couldn't be read. + /// true if the limit was read successfully; otherwise, false. + internal static bool TryGetMemoryLimitV2(string? currentCGroupMemoryPath, string? cgroupMemoryHierarchyMountPath, out ulong limit) { bool foundAnyLimit = false; ulong minLimit = ulong.MaxValue; - string? currentCGroupMemoryPath = s_cgroupMemoryPath; - string? cgroupMemoryHierarchyMountPath = s_cgroupMemoryHierarchyMountPath; if (currentCGroupMemoryPath != null && cgroupMemoryHierarchyMountPath != null) { // Iterate over the directory hierarchy representing the cgroup hierarchy until reaching the - // mount directory. The mount directory doesn't contain the memory.max. - do + // mount directory. The mount directory can contain memory.max when it is not the global root. + while (currentCGroupMemoryPath != null && IsPathAtOrBelowMount(currentCGroupMemoryPath, cgroupMemoryHierarchyMountPath)) { if (TryReadMemoryValueFromFile(currentCGroupMemoryPath + "/memory.max", out ulong currentLevelLimit)) { @@ -135,9 +143,13 @@ internal static bool TryGetMemoryLimitV2(out ulong limit) minLimit = currentLevelLimit; } } + if (currentCGroupMemoryPath == cgroupMemoryHierarchyMountPath) + { + break; + } + currentCGroupMemoryPath = Path.GetDirectoryName(currentCGroupMemoryPath); } - while (currentCGroupMemoryPath!.Length != cgroupMemoryHierarchyMountPath.Length); } limit = minLimit; @@ -145,6 +157,18 @@ internal static bool TryGetMemoryLimitV2(out ulong limit) return foundAnyLimit; } + private static bool IsPathAtOrBelowMount(string path, string mount) + { + if (!path.StartsWith(mount, StringComparison.Ordinal)) + { + return false; + } + + return path.Length == mount.Length || + mount == "/" || + path[mount.Length] == '/'; + } + /// Tries to parse a memory limit from the specified file. /// The path to the file to parse. /// The parsed result, or 0 if it couldn't be parsed. diff --git a/src/libraries/Common/tests/Tests/Interop/cgroupsTests.cs b/src/libraries/Common/tests/Tests/Interop/cgroupsTests.cs index 3ad0b3cee4d150..0d05a6b11b9d70 100644 --- a/src/libraries/Common/tests/Tests/Interop/cgroupsTests.cs +++ b/src/libraries/Common/tests/Tests/Interop/cgroupsTests.cs @@ -34,11 +34,41 @@ public void ValidateTryReadMemoryValue(bool expectedResult, string valueText, ul [Theory] [InlineData("/sys/fs/cgroup/cpu/my_cgroup", "/docker/1234", "/sys/fs/cgroup/cpu", "/docker/1234/my_cgroup")] [InlineData("/sys/fs/cgroup/cpu/my_cgroup", "/", "/sys/fs/cgroup/cpu", "/my_cgroup")] + [InlineData("/sys/fs/cgroup", "/some/container", "/sys/fs/cgroup", "/some/container")] public void ValidateFindCGroupPath(string expectedResult, string hierarchyRoot, string hierarchyMount, string cgroupPathRelativeToMount) { Assert.Equal(expectedResult, Interop.cgroups.FindCGroupPath(hierarchyRoot, hierarchyMount, cgroupPathRelativeToMount)); } + [Fact] + public void ValidateTryGetMemoryLimitV2AtHierarchyMount() + { + string testRoot = GetTestFilePath(); + string hierarchyMount = Path.Combine(testRoot, "mount"); + Directory.CreateDirectory(hierarchyMount); + File.WriteAllText(Path.Combine(hierarchyMount, "memory.max"), "1"); + + Assert.True(Interop.cgroups.TryGetMemoryLimitV2(hierarchyMount, hierarchyMount, out ulong limit)); + Assert.Equal(1UL, limit); + } + + [Fact] + public void ValidateTryGetMemoryLimitV2StopsAtHierarchyMount() + { + string testRoot = GetTestFilePath(); + string hierarchyMount = Path.Combine(testRoot, "mount"); + string parentCGroup = Path.Combine(hierarchyMount, "a"); + string currentCGroup = Path.Combine(parentCGroup, "b"); + Directory.CreateDirectory(currentCGroup); + File.WriteAllText(Path.Combine(hierarchyMount, "memory.max"), "20"); + File.WriteAllText(Path.Combine(parentCGroup, "memory.max"), "20"); + File.WriteAllText(Path.Combine(currentCGroup, "memory.max"), "10"); + File.WriteAllText(Path.Combine(testRoot, "memory.max"), "2"); + + Assert.True(Interop.cgroups.TryGetMemoryLimitV2(currentCGroup, hierarchyMount, out ulong limit)); + Assert.Equal(10UL, limit); + } + [Theory] [InlineData(true, 2, "0 0 0:0 / /foo ignore ignore - cgroup2 cgroup2 ignore", "ignore", "/", "/foo")] [InlineData(true, 2, "0 0 0:0 / /foo ignore ignore - cgroup2 cgroup2 ignore", "memory", "/", "/foo")]