@@ -12,6 +12,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
+#include <sys/mman.h>
#include <unistd.h>
#include "cgroup_util.h"
@@ -456,22 +457,28 @@ int get_temp_fd(void)
return open(".", O_TMPFILE | O_RDWR | O_EXCL);
}
+int touch_file(int fd, size_t size, off_t offset) {
+ char *buf;
+
+ buf = mmap(0, size, PROT_READ, MAP_SHARED | MAP_POPULATE, fd, offset);
+ if (!buf)
+ return -1;
+ munmap(buf, size);
+ return 0;
+}
+
int alloc_pagecache(int fd, size_t size)
{
- char buf[PAGE_SIZE];
struct stat st;
- int i;
if (fstat(fd, &st))
goto cleanup;
- size += st.st_size;
-
- if (ftruncate(fd, size))
+ if (ftruncate(fd, st.st_size + size))
goto cleanup;
- for (i = 0; i < size; i += sizeof(buf))
- read(fd, buf, sizeof(buf));
+ if (touch_file(fd, size, st.st_size))
+ goto cleanup;
return 0;
@@ -42,6 +42,7 @@ extern int cg_run_nowait(const char *cgroup,
int (*fn)(const char *cgroup, void *arg),
void *arg);
extern int get_temp_fd(void);
+extern int touch_file(int fd, size_t size, off_t offset);
extern int alloc_pagecache(int fd, size_t size);
extern int alloc_anon(const char *cgroup, void *arg);
extern int is_swap_enabled(void);
@@ -20,6 +20,7 @@
#include "../kselftest.h"
#include "cgroup_util.h"
+
/*
* This test creates two nested cgroups with and without enabling
* the memory controller.
@@ -579,6 +580,24 @@ static int alloc_pagecache_max_30M(const char *cgroup, void *arg)
}
+static int touch_pagecache(const char *cgroup, void *arg) {
+ int fd = (long)arg;
+ struct stat st;
+
+ if (fstat(fd, &st))
+ return -1;
+
+ return touch_file(fd, st.st_size, 0);
+}
+
+static int alloc_pagecache_300M(const char *cgroup, void *arg)
+{
+ int fd = (long)arg;
+
+ return alloc_pagecache(fd, MB(300));
+}
+
+
/*
* This test checks that memory.high limits the amount of
* memory which can be consumed by either anonymous memory
@@ -1169,6 +1188,82 @@ static int test_memcg_oom_group_score_events(const char *root)
return ret;
}
+/*
+ * First, this test creates the following hierarchy:
+ * A memory.low = 200M, memory.max = 200M
+ * A/B memory.low = 200M, memory.max = 200M
+ * A/C memory.low = 200M, memory.max = 200M
+ *
+ * Then it triggers relaim in A, by allocating A/B and A/C
+ * pagecaches. During this reclaim elow values of A/B and A/C are
+ * filled.
+ * Then it lowers limits for A/C to create memory pressure
+ * and trigger reclaim in A/C. Usage of elow value assigned during
+ * previous step as memory protection will lead with high probabilty
+ * to OOM.
+ */
+static int test_memcg_stale_protection_oom(const char *root) {
+ int i;
+ int fd = -1;
+ char *memcgs[3] = {0};
+ const int memcgs_count = sizeof(memcgs)/sizeof(memcgs[0]);
+ int ret = -1;
+ char *low1 = "200M", *low2 = "100M";
+
+ for (i = 0; i < memcgs_count; ++i) {
+ char name[] = "memcg_test_\0";
+ name[sizeof(name)-2] = '0' + i;
+ memcgs[i] = cg_name(memcgs[0] ? : root, name);
+ if (!memcgs[i])
+ goto cleanup;
+ if (cg_create(memcgs[i]))
+ goto cleanup;
+ }
+
+ if (cg_write(memcgs[0], "cgroup.subtree_control", "+memory"))
+ goto cleanup;
+
+ for (i = 0; i < memcgs_count; ++i) {
+ if (cg_write(memcgs[i], "memory.max", low1)) {
+ goto cleanup;
+ }
+ if (cg_write(memcgs[i], "memory.low", low1)) {
+ goto cleanup;
+ }
+ }
+
+ fd = get_temp_fd();
+ if (fd < 0)
+ goto cleanup;
+
+ if (cg_run(memcgs[1], alloc_pagecache_300M, (void*)(long)fd)) {
+ goto cleanup;
+ }
+ if (cg_run(memcgs[2], touch_pagecache, (void*)(long)fd)) {
+ goto cleanup;
+ }
+
+ if (cg_write(memcgs[2], "memory.max", low2))
+ goto cleanup;
+ if (cg_write(memcgs[2], "memory.low", low2))
+ goto cleanup;
+ if (cg_run(memcgs[2], touch_pagecache, (void*)(long)fd))
+ goto cleanup;
+
+ ret = 0;
+cleanup:
+ for (i = memcgs_count; i > 0; --i) {
+ if (memcgs[i - 1]) {
+ cg_destroy(memcgs[i - 1]);
+ free(memcgs[i - 1]);
+ }
+ }
+
+ if (fd >= 0)
+ close(fd);
+ return ret;
+}
+
#define T(x) { x, #x }
struct memcg_test {
@@ -1187,6 +1282,7 @@ struct memcg_test {
T(test_memcg_oom_group_leaf_events),
T(test_memcg_oom_group_parent_events),
T(test_memcg_oom_group_score_events),
+ T(test_memcg_stale_protection_oom),
};
#undef T
Add test case that reproduces premature oom caused by usage of stale mem protection. Usage of stale mem protection could lead to throttle of reclaim in target memcg and thus could trigger OOM. This bug was fixed in 22f7496f0b90 ("mm, memcg: avoid stale protection values when cgroup is above protection"). Signed-off-by: Alexander Ovechkin <ovov@yandex-team.ru> --- tools/testing/selftests/cgroup/cgroup_util.c | 21 ++-- tools/testing/selftests/cgroup/cgroup_util.h | 1 + .../selftests/cgroup/test_memcontrol.c | 96 +++++++++++++++++++ 3 files changed, 111 insertions(+), 7 deletions(-)