From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail.openvz.org (mail.openvz.org [69.168.225.77]) by lore.virtuozzo.com (Postfix) with ESMTPS id E65C880E9D for ; Wed, 28 Oct 2026 12:23:49 +0000 (UTC) Received: from mail.openvz.org (localhost [127.0.0.1]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 69SCMa4a019895; Wed, 28 Oct 2026 15:22:37 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 69SCMa4a019895 Authentication-Results: mail.openvz.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=virtuozzo.com header.i=@virtuozzo.com header.b="W9FnHuY1" Received: from mail-wr1-f69.google.com (mail-wr1-f69.google.com [209.85.221.69]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 69SCMM7U019873 (version=TLSv1/SSLv3 cipher=AES128-GCM-SHA256 bits=128 verify=FAIL) for ; Wed, 28 Oct 2026 15:22:22 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 69SCMM7U019873 Received: by mail-wr1-f69.google.com with SMTP id ffacd0b85a97d-48c430f0f01so743221f8f.1 for ; Wed, 28 Oct 2026 05:22:22 -0700 (PDT) X-Forwarded-Encrypted: i=2; AKwUvBwEaVXu4HBtVJR2x5QR/rIKk9178BSEJJ+gJpnVGNJ85+tRFsOfY3HLVDt2snx8cEkYWcbGUg==@openvz.org X-Gm-Message-State: AFq9FYKrWV+hXJLycJbnFXKpTe2T5X8BTIdiudJ7juZAwgwNtj4pjxJB rPyzQtqiZRYsZAq8pKGbIInUC99PE5Tou6jaTB6XDiB0VWT9DDc0kkob1yBY0AYiH1UoWhJc1mE YBzgyDgDG+PmSo6VpShSSEYFItCdPWRXQksGOsV/DDb5kq58WBwrWOw== X-Gm-Gg: AYBFou2pL+MJKGOV0G6skPAIVmTdMm93+uN67jJPI2d3zWXpenlWRSDe/r4Skk6o38B 72b0Jd42VSG9UxjtIGQPW/DBoAvtqnka7S6yRDgGSrQjN5vPG7+Gs7Q9yK3FZ0+IXz1OeYHjuQ5 +C8Ih+sCHMxMaUloudQmBMbBES3dv8/dWJHek2D5xA6KaTZ1VbktMhBSQYxa7cwWvTqusa8VUty osYWfFibLu71vsNcmDej9e85P853rA231RMHThG7CGnBQ0uxTGmztDs78kISY2qLgf6X94CqKel xCUXJkfbPXHOT/eR1e6xdZuLfm1b+QWKt3JvzDq0FjhsmNnrJ9U5utR7zNoIGsRc4FAKOrAdpF2 OrxRsRpzBzNCi2Yl+Fg== X-Received: by 2002:a05:6000:46d4:b0:48e:3697:708d with SMTP id ffacd0b85a97d-48e5d44e62bmr3705323f8f.12.1793190142034; Wed, 28 Oct 2026 05:22:22 -0700 (PDT) X-Received: by 2002:a05:6000:46d4:b0:48e:3697:708d with SMTP id ffacd0b85a97d-48e5d44e62bmr3705264f8f.12.1793190141262; Wed, 28 Oct 2026 05:22:21 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1793190141; cv=none; d=google.com; s=arc-20260327; b=X5ed24RcuszUreC+yTcN/YwQD5JZG5xlF2+feepqtbpaLxKXZ/qnpPPET6wdHnzvHG gzp7Fh7PJL+6QqKUtWplYRHm5FUksF6c+YrXV88rWImk/znJJKGhExx1VdS1X3gI8oN1 KaSA9lwzRWZW0t8LrvyLqlK/7pb89fwri8f4O3uRnquZLAJ/WFZnydKKN544gIRKQ5hM 1nx9yTr/BZxd43xUNYKFgNQA2/4CzUNXm2hqqHEJNDJ+p+YBs1sUEMcoscSMjkq3JphV MZZIZ+k6HMBNx7QML1FB06ZBeIoIzdp37i0w/r1gFczSXmVm07ApPLIxDNaho9ygzKA/ w7Ew== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:dkim-signature; bh=9tG9nCNYkODaC66rvnKXJz2yUlNK2tAT9RCe+8e97zw=; fh=XcqC0Cib/C4XsQyLUc3b6E9Ii37Wk0LS3yBNHj5szHI=; b=RrHDfZthSBgLyaTK7isxg9ELxlfhdTHX7YFQITltedf+d01qJtXedB3h20IQ3SbNz+ 4XDtVNkQ/srlW9zHuTBgdaEN6Gu7/GpjOicl2kd+uIL7KmcoMjccAaBomWU2SSzyq8S7 grWO+NRr1tGxRaZJvtFuwMmhzNeQdMiboi65rICoLHUyVsxgGzJWQvUASz9IkISPf09d kb4F6y7aAZQf5mPGL2A9gGw5S3dqb258tP9lAr2BCTkThRZGH+dzRefELTyNckzAUQEd NlcrFrrsH+bet6zW1UWmNMmmYPC+TWriIlpgm3FHwaLwqmo4WOnVVBrB8wpahpEfvdjZ Vl0Q==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=W9FnHuY1; spf=pass (google.com: domain of khorenko@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=khorenko@virtuozzo.com; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com Received: from relay.virtuozzo.com (relay.virtuozzo.com. [130.117.225.111]) by mx.google.com with ESMTPS id ffacd0b85a97d-48e3ecf612csi23807728f8f.246.2026.10.28.05.22.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 28 Oct 2026 05:22:21 -0700 (PDT) Received-SPF: pass (google.com: domain of khorenko@virtuozzo.com designates 130.117.225.111 as permitted sender) client-ip=130.117.225.111; Authentication-Results: mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=W9FnHuY1; spf=pass (google.com: domain of khorenko@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=khorenko@virtuozzo.com; dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=virtuozzo.com DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=virtuozzo.com; s=relay; h=Message-Id:Content-Type:MIME-Version:Subject:Date :From; bh=9tG9nCNYkODaC66rvnKXJz2yUlNK2tAT9RCe+8e97zw=; b=W9FnHuY1Y6TQwl/dTzh C1UGFkTA0i2DM1cQHPP/aTsCX/b5J8gmoMpmzPSKwmRHVNwATtbCe++VsR46T3kRIqQWIVdd1KILD ZDYWIqq94PMPO7fdQcH9cgpDo7Neeiu2puJuQZzzFvMoEl12Su9BWKgHrZh5KzWm4Gu65fM8mJTS8 deqPHsL8yDT/4AOvIsV53K7RbwK6ikEYAy2b89eGIxx/4IRjTOuDzI3J8/18UV5wQWISrd9LHkQZ2 S9Lhk3ojj6hFNB5NeWMkabkWEQpVqM7mdbvPf0W/M1VeC1/aujB++m5sten2wR7sY2LEX4pyUltMv ZiwfsYZhpKyyp7A==; Received: from ch-demo-asa.virtuozzo.com ([130.117.225.8] helo=f0.sw.ru) by relay.virtuozzo.com with esmtp (Exim 4.96) (envelope-from ) id 1xM2bq-007ETm-2H; Wed, 28 Oct 2026 13:22:20 +0100 From: Konstantin Khorenko Date: Wed, 28 Oct 2026 13:22:18 +0100 MIME-Version: 1.0 Message-Id: <20261028-b4-prep-khorenko-vstor-151607-numa-migrate-order-v1-2-268336526677@virtuozzo.com> References: <20261028-b4-prep-khorenko-vstor-151607-numa-migrate-order-v1-0-268336526677@virtuozzo.com> In-Reply-To: <20261028-b4-prep-khorenko-vstor-151607-numa-migrate-order-v1-0-268336526677@virtuozzo.com> To: Pavel Tikhomirov X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=10592; i=khorenko@virtuozzo.com; h=from:subject:message-id; bh=qNLjt9GBZ2UjPcHaJlBjnwnLcWoRMeP44bjDCQQXTDg=; b=owEBbQKS/ZANAwAKAVGWCkf5YVwqAcsmYgBq4ej7FKgQVW/Gbie/gMXSNvxfWaXU+CM7zEY3g 6BdP70W/IGJAjMEAAEKAB0WIQRWD23IPcXT0GO9CCxRlgpH+WFcKgUCauHo+wAKCRBRlgpH+WFc KncJD/9LcrAelLzxUMNYvoGVY28uQ9j89fF7giNRagC6X6izlcamRXZiE/jmE626n1tIbOjUVkO ReXrq7OiZ50yKEgdRZ1sPfAKMfRP1ASpAzB4TUnKNrlhLnEDbeCJ7T51fprAByI/LyI0M4bMPzy rSqt/YKy9xQOAaJqpn/bTEXpecW9Yrya4uoZghP/umhgBijDjxCjuW65xqPVpMun+CjRdBp9w1i 5QkBQqT1rj6L8Wt9lWP1v5dj3V7jZxkTqlofyl1E1CnA9xkF50SV5xOy5R4x4ZHef3+pArt9MZd KeDu4LIAO1c69UhrsOrbZhKbLuc5As3acyObxSWofAiyiWrqhje8FGD2zYop6rgImt/HcRzNPq9 +CFuDiEfUFIT+1CZs9EHljU1+5fbjymUm2KF+q83kgG5w5hPaYt+3288TNghb3cfEAv6mc7CpM6 IY5IyawvYAZSFxPT3CNRLXdhayr+cXPulRvkcT7qLEPtatTRPqmeB+9F/9o6cLr4+cKEBWJ2TVF a+qVPXhlISyV59Kwwu2iRSwS+Nl+rd59NI6JJ5Qkd+XzPBeZD3O4Fml9kmyAIkaMUnsK+H9Nx0r LVwfO2+DbwolqBUiHPaNglZ383BN6rse14qZNkmkDrMGJSnWTSXH5UbzWKR7372+cthGZlNOuPM uCrVzJC02goyIcA== X-Developer-Key: i=khorenko@virtuozzo.com; a=openpgp; fpr=560F6DC83DC5D3D063BD082C51960A47F9615C2A X-OZ-Fwd: true Cc: devel@openvz.org Subject: [Devel] [PATCH hci-8.1 2/2] selftests/ve: check memory.numa_migrate keeps the folio order X-BeenThere: devel@openvz.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: OpenVZ development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Sender: devel-bounces@openvz.org Errors-To: devel-bounces@openvz.org memcg_numa_migrate_new_folio() used to allocate a PMD-sized destination for every large folio. A page cache folio of a smaller order then ended up with a 512-page destination carrying its index and reference count. Only the source pages were copied into it, so a read of the folios that followed in the same 2 MB range was served from the uninitialised part of the destination, and the inode died in clear_inode() on eviction. The test writes a file on an XFS image, where 64K writes produce large folios, from a cgroup bound to one NUMA node, migrates the cgroup to a second node, checks that something was migrated and reads the file back against the written pattern. On an unfixed kernel the comparison fails; the test then leaves the image mounted, because unlinking or unmounting would evict the inode and crash the node. It needs root, two NUMA nodes with memory (numa=fake=2 is enough), cgroup2 with the memory and cpuset controllers, and mkfs.xfs, and skips otherwise. https://virtuozzo.atlassian.net/browse/VSTOR-151607 Feature: ve selftests Signed-off-by: Konstantin Khorenko --- tools/testing/selftests/ve/Makefile | 1 + tools/testing/selftests/ve/ve_numa_migrate_test.c | 304 ++++++++++++++++++++++ 2 files changed, 305 insertions(+) diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile index 51f9316beb99b..e96320a183844 100644 --- a/tools/testing/selftests/ve/Makefile +++ b/tools/testing/selftests/ve/Makefile @@ -6,6 +6,7 @@ TEST_GEN_PROGS += ve_ns_owner_test TEST_GEN_PROGS += ve_perms_test TEST_GEN_PROGS += ve_mount_accounting_test TEST_GEN_PROGS += ve_vtty_stress +TEST_GEN_PROGS += ve_numa_migrate_test # CPUID faulting is x86-64 only. ARCH says "x86" for 32-bit too and lib.mk # picks the clang target by its emptiness, so decide by the compiler. diff --git a/tools/testing/selftests/ve/ve_numa_migrate_test.c b/tools/testing/selftests/ve/ve_numa_migrate_test.c new file mode 100644 index 0000000000000..5fbf4af49f7fc --- /dev/null +++ b/tools/testing/selftests/ve/ve_numa_migrate_test.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * memory.numa_migrate must give a large page cache folio a destination of + * the same order. An oversized destination inherits the index and the + * reference count of the source, only the source pages are copied into it, + * reads of the neighbouring folios land in its uninitialised part, and the + * inode dies in clear_inode() on eviction. + * + * Write a file on an XFS image (XFS allocates large folios for 64K writes) + * from a cgroup bound to one NUMA node, migrate the cgroup to another node + * and check that the data reads back intact. + * + * Needs root, two NUMA nodes with memory (numa=fake=2 will do), cgroup2 + * with the memory and cpuset controllers, and mkfs.xfs. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest_harness.h" +#include "ve_selftest.h" + +#define CG_NAME "ve_numa_migrate_test" +#define DIR_TEMPLATE "/var/tmp/ve_numa_migrate.XXXXXX" +/* mkfs.xfs refuses anything smaller; the image is sparse. */ +#define IMG_SIZE (320UL << 20) +#define FILE_SIZE (4UL << 20) +#define CHUNK_SIZE (64UL << 10) + +FIXTURE(numa_migrate) +{ + int cgv2_fd; + int cg_fd; + int node_from; + int node_to; + char dir[64]; + char img[80]; + char mnt[80]; + int mounted; + int leave_mounted; + int data_fd; +}; + +/* First two node ids of a sysfs node list such as "0-1" or "0,2-3". */ +static int two_nodes_with_memory(int *from, int *to) +{ + char buf[256], *tok, *save; + int found = 0; + + if (read_file_at(AT_FDCWD, "/sys/devices/system/node/has_memory", + buf, sizeof(buf)) <= 0) + return -1; + + for (tok = strtok_r(buf, ",\n", &save); tok && found < 2; + tok = strtok_r(NULL, ",\n", &save)) { + int a, b, n; + + if (sscanf(tok, "%d-%d", &a, &b) != 2) { + if (sscanf(tok, "%d", &a) != 1) + return -1; + b = a; + } + for (n = a; n <= b && found < 2; n++) { + if (found == 0) + *from = n; + else + *to = n; + found++; + } + } + return found == 2 ? 0 : -1; +} + +static int run_cmd(const char *fmt, ...) +{ + char cmd[256]; + va_list ap; + int status; + + va_start(ap, fmt); + vsnprintf(cmd, sizeof(cmd), fmt, ap); + va_end(ap); + + status = system(cmd); + if (status < 0 || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +/* Bytes of file pages the cgroup has on @node, from memory.numa_stat. */ +static int file_bytes_on_node(int cg_fd, int node, unsigned long long *out) +{ + static char buf[32768]; + char key[16], *p; + + if (read_file_at(cg_fd, "memory.numa_stat", buf, sizeof(buf)) <= 0) + return -1; + p = strstr(buf, "\nfile "); + if (!p) + return -1; + snprintf(key, sizeof(key), " N%d=", node); + p = strstr(p, key); + if (!p) + return -1; + *out = strtoull(p + strlen(key), NULL, 10); + return 0; +} + +static void cleanup(FIXTURE_DATA(numa_migrate) *self) +{ + char path[PATH_MAX]; + + if (self->data_fd >= 0) { + close(self->data_fd); + self->data_fd = -1; + } + if (self->dir[0]) { + snprintf(path, sizeof(path), "%s/data", self->mnt); + unlink(path); + if (self->mounted) + umount(self->mnt); + unlink(self->img); + rmdir(self->mnt); + rmdir(self->dir); + self->dir[0] = '\0'; + } + if (self->cg_fd >= 0) { + close(self->cg_fd); + self->cg_fd = -1; + } + if (self->cgv2_fd >= 0) { + /* The test task may still sit in the cgroup after a failure. */ + write_u64_at(self->cgv2_fd, "cgroup.procs", getpid()); + unlinkat(self->cgv2_fd, CG_NAME, AT_REMOVEDIR); + close(self->cgv2_fd); + self->cgv2_fd = -1; + } +} + +/* + * Returns 0, or 1 with everything undone when the host cannot run the test. + * The harness runs no teardown after a failed setup, so the undo is here. + */ +static int prepare(FIXTURE_DATA(numa_migrate) *self, const char **why) +{ + int fd; + + self->cgv2_fd = mount_cg2_fd(); + if (self->cgv2_fd < 0) { + *why = "cannot mount cgroup2"; + goto fail; + } + if (write_file_at(self->cgv2_fd, "cgroup.subtree_control", + "+memory +cpuset")) { + *why = "cannot enable the memory and cpuset controllers"; + goto fail; + } + if (mkdirat(self->cgv2_fd, CG_NAME, 0755) && errno != EEXIST) { + *why = "cannot create the test cgroup"; + goto fail; + } + self->cg_fd = openat(self->cgv2_fd, CG_NAME, O_PATH | O_DIRECTORY); + if (self->cg_fd < 0) { + *why = "cannot open the test cgroup"; + goto fail; + } + if (faccessat(self->cg_fd, "memory.numa_migrate", W_OK, 0)) { + *why = "no memory.numa_migrate"; + goto fail; + } + if (write_u64_at(self->cg_fd, "cpuset.mems", self->node_from)) { + *why = "cannot bind the cgroup to a node"; + goto fail; + } + + snprintf(self->dir, sizeof(self->dir), DIR_TEMPLATE); + if (!mkdtemp(self->dir)) { + self->dir[0] = '\0'; + *why = "cannot create a temporary directory"; + goto fail; + } + snprintf(self->img, sizeof(self->img), "%s/img", self->dir); + snprintf(self->mnt, sizeof(self->mnt), "%s/mnt", self->dir); + if (mkdir(self->mnt, 0755)) { + *why = "cannot create the mount point"; + goto fail; + } + fd = open(self->img, O_CREAT | O_RDWR, 0600); + if (fd < 0) { + *why = "cannot create the image file"; + goto fail; + } + if (ftruncate(fd, IMG_SIZE)) { + close(fd); + *why = "cannot size the image file"; + goto fail; + } + close(fd); + if (run_cmd("mkfs.xfs -q -f %s", self->img)) { + *why = "mkfs.xfs failed"; + goto fail; + } + if (run_cmd("mount -o loop %s %s", self->img, self->mnt)) { + *why = "cannot loop-mount the XFS image"; + goto fail; + } + self->mounted = 1; + return 0; +fail: + cleanup(self); + return 1; +} + +FIXTURE_SETUP(numa_migrate) +{ + const char *why; + + self->cgv2_fd = -1; + self->cg_fd = -1; + self->data_fd = -1; + self->dir[0] = '\0'; + + if (geteuid()) + SKIP(return, "needs root"); + if (two_nodes_with_memory(&self->node_from, &self->node_to)) + SKIP(return, "needs two NUMA nodes with memory"); + if (access("/usr/sbin/mkfs.xfs", X_OK) && access("/sbin/mkfs.xfs", X_OK)) + SKIP(return, "needs mkfs.xfs"); + if (prepare(self, &why)) + SKIP(return, "%s", why); +} + +FIXTURE_TEARDOWN(numa_migrate) +{ + if (self->leave_mounted) { + TH_LOG("leaving %s mounted: evicting the file would hit " + "BUG_ON(nrpages) in clear_inode()", self->mnt); + self->dir[0] = '\0'; + } + cleanup(self); +} + +TEST_F(numa_migrate, large_folio_order) +{ + unsigned long long on_to_before, on_to_after; + unsigned char *buf, *check; + char path[PATH_MAX], list[64]; + unsigned long off; + int fd; + + ASSERT_EQ(write_u64_at(self->cg_fd, "cgroup.procs", getpid()), 0); + + buf = malloc(CHUNK_SIZE); + check = malloc(CHUNK_SIZE); + ASSERT_NE(buf, NULL); + ASSERT_NE(check, NULL); + + snprintf(path, sizeof(path), "%s/data", self->mnt); + fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0600); + ASSERT_GE(fd, 0); + self->data_fd = fd; + for (off = 0; off < FILE_SIZE; off += CHUNK_SIZE) { + memset(buf, (off / CHUNK_SIZE) & 0xff, CHUNK_SIZE); + ASSERT_EQ(pwrite(fd, buf, CHUNK_SIZE, off), (ssize_t)CHUNK_SIZE); + } + ASSERT_EQ(fsync(fd), 0); + + ASSERT_EQ(file_bytes_on_node(self->cg_fd, self->node_to, + &on_to_before), 0); + + /* From here on a failure means eviction would crash the kernel. */ + self->leave_mounted = 1; + snprintf(list, sizeof(list), "%d,%d", self->node_from, self->node_to); + ASSERT_EQ(write_file_at(self->cg_fd, "cpuset.mems", list), 0); + ASSERT_EQ(write_u64_at(self->cg_fd, "memory.numa_migrate", + self->node_to), 0); + + ASSERT_EQ(file_bytes_on_node(self->cg_fd, self->node_to, + &on_to_after), 0); + TH_LOG("file bytes on node %d: %llu -> %llu", self->node_to, + on_to_before, on_to_after); + ASSERT_GE(on_to_after, on_to_before + FILE_SIZE); + + for (off = 0; off < FILE_SIZE; off += CHUNK_SIZE) { + memset(buf, (off / CHUNK_SIZE) & 0xff, CHUNK_SIZE); + ASSERT_EQ(pread(fd, check, CHUNK_SIZE, off), (ssize_t)CHUNK_SIZE); + ASSERT_EQ(memcmp(buf, check, CHUNK_SIZE), 0); + } + self->leave_mounted = 0; + + free(buf); + free(check); +} + +TEST_HARNESS_MAIN -- 2.43.0 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel