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 E64C780EB5 for ; Wed, 28 Oct 2026 13:11:27 +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 69SDA1RY020228; Wed, 28 Oct 2026 16:10:04 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 69SDA1RY020228 Authentication-Results: mail.openvz.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=virtuozzo.com header.i=@virtuozzo.com header.b="ZklvLQos" Received: from mail-wr1-f72.google.com (mail-wr1-f72.google.com [209.85.221.72]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 69SDA0qZ020217 (version=TLSv1/SSLv3 cipher=AES128-GCM-SHA256 bits=128 verify=FAIL) for ; Wed, 28 Oct 2026 16:10:00 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 69SDA0qZ020217 Received: by mail-wr1-f72.google.com with SMTP id ffacd0b85a97d-48e1ad3cb66so584896f8f.3 for ; Wed, 28 Oct 2026 06:10:00 -0700 (PDT) X-Forwarded-Encrypted: i=2; AKwUvBxxMN6soPCMS2QAT3oJG0rdapEODYqplp4hw452w6S7CAVggcgno5GPT91HFUfz0Q0wVABg3w==@openvz.org X-Gm-Message-State: AFq9FYKWK8mdLrhloV4zbVGfUy5o8qJh3zv/HWe2nCq/GFgIC4xwf5S1 XkOMD/LVvre1giDdjFNQmZN10nmqsx97FzoEKFqcKkGyw7pl4YDwwIEJjWr/JylxTiIjvmukpmr 8pLSAnAzPkuIQolosYgiVh+E7YiEwixfRQQvb0NTqR+KsRjZwoCatXA== X-Gm-Gg: AYBFou2BVlH5fKefLwYvYeocTMkpR/e+IZIVCVfaqi1YxU3yoTOyJ7vx0BAfdB+N0k8 yuwKGbZns1OSAGrmDB1zHxSAsmAJJr6onos3l2+Kltav5PaTWSJui6yNRZLTni6gDzlp3spLell ctuR0SDx4cGXX/bgs89y1sxnX896EnaVP5W7uOjDEj/JYzDwo92h8joT1TbSGmBPjsmVxa/h+Vc 93mLLEdDdA2G4I0HFfq95B9npgkZtg1oDS1yx3Yyoqb/EzyWneemyHBLG1OOHbsovHVVo0VZso6 ODG+Fkj3u3lFKS4BhR1Q1hTEG2tp6DfuyPe2teOkgHGT3TCvtFCxti7PSnKxTzY5gu24uH8h9La Gp1AyenkPClq7FFAn3JMqofODnfTDBQ== X-Received: by 2002:a5d:4841:0:b0:48b:451:4f19 with SMTP id ffacd0b85a97d-48e5d3120admr4377112f8f.51.1793193000069; Wed, 28 Oct 2026 06:10:00 -0700 (PDT) X-Received: by 2002:a5d:4841:0:b0:48b:451:4f19 with SMTP id ffacd0b85a97d-48e5d3120admr4377056f8f.51.1793192999537; Wed, 28 Oct 2026 06:09:59 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1793192999; cv=none; d=google.com; s=arc-20260327; b=qTXm7YTfC53muSanNnHqLUjeQLg/w8rQ33I1A3OkXhO6MMWqcNfgbKyHm7VT8N9onR 7m5Sq8UZ7eT3/Se++zyakzYZeIrv6mwk18I5koOUYdYc2J7ufUjrJCdkJi6Vhslt9hc5 pXOXa9Kr9GCAqcskSDhUzpA8l3GqEGNilDLIn9/k/0idBQKuVsjy2BBhmTTceBbtVhpv JCYZmUY3BfiYCRxTrz+kK6bvl3NECkyZgaZ86PfJdUjilTigyGpu02tXiJW10PzLFI1y 97KAOGT2/2GoquALDsPzB1ueu/oTd2EP8sqOgjzpecEPAcqjeFDqdXZv5HFJGZHLws8M GpgQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=message-id:date:references:in-reply-to:cc:to:from:subject :content-transfer-encoding:mime-version:dkim-signature; bh=zEgvPz7LM7rtyNiPSjlRu8QsQULxfawPNWgA8VxcsaA=; fh=iW+EeVBgw6gMfBKTMQNjVEaljb3qEpbHxtkEro0NxH8=; b=skZH33XAD9baB/yQbfthil3LEWBeaMsY1Z+2ZGDUGNCj0Dx355KpghG5T7cDqzqByU QtNRm3sE8rDMw9pAGBGqk0DN2aN7UBsJ08yDVjkS9jEJrUtAQFRHyKj4R6gpU4Ty0IiK GrknCyg5nL24lz3pwdpOUfA27cie3RHn9UuTgLpWSEOr4RuFSqfz73BKbOVBu22XgGTr FFdKHQ1HjWKj0HNJRsvDx7vAx0WGFByuZ6sOBjlum/wpZPxT/OEdkpUCxEu4exS8iEw2 Tofq5U+ttDKVTt2/25YH6tIvZ/l2OOFy2U4P8x+9Pihs3d5jNx9L/14GCUlbfXvUVESx njXA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=ZklvLQos; spf=pass (google.com: domain of vasileios.almpanis@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=vasileios.almpanis@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-48e3ed21e85si24177380f8f.438.2026.10.28.06.09.59 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 28 Oct 2026 06:09:59 -0700 (PDT) Received-SPF: pass (google.com: domain of vasileios.almpanis@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=ZklvLQos; spf=pass (google.com: domain of vasileios.almpanis@virtuozzo.com designates 130.117.225.111 as permitted sender) smtp.mailfrom=vasileios.almpanis@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:Date:From:Subject:Content-Type: MIME-Version; bh=zEgvPz7LM7rtyNiPSjlRu8QsQULxfawPNWgA8VxcsaA=; b=ZklvLQosopub sBm6eJpyTm+YHQtEH2Siyyx9aaY4UTGtmQx6DmOUpsGrkCru/LxbdcaaOgLH45OGhRQP2dbidvOA8 GWdRKOmPccrYf0G6aHSQgFWfYxWsBARZXF5UpNImZ08hnnu9DJF/s+KLcQiUxW5UTaY6sjZp+ZnEC uIY1G/r6CUONN2z4jaRppGwXtxq9pW/7AXG1J1U5KbQYcrKtWDC/2mZDhhQUHxy7pjgwleDbHno6j x16yNGfxXvp1Lm7cVs62guwzfITQqInNLEt8Zboia5IbOD1bNNZ4YRq+9F4/PfyDzg2R52BUkIJOq pSfEptdnJ1KHEZueiBthyQ==; Received: from [130.117.225.5] (helo=dev010.ch-qa.vzint.dev) by relay.virtuozzo.com with esmtp (Exim 4.96) (envelope-from ) id 1xM3Lw-008HWi-39; Wed, 28 Oct 2026 14:09:58 +0100 MIME-Version: 1.0 From: Vasileios Almpanis To: Konstantin Khorenko In-Reply-To: <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> <20261028-b4-prep-khorenko-vstor-151607-numa-migrate-order-v1-2-268336526677@virtuozzo.com> Date: Wed, 28 Oct 2026 13:09:57 +0000 Message-Id: <179319299768.343268.8435037349385138555.b4-review@b4> X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1793192999; l=10880; i=vasileios.almpanis@virtuozzo.com; s=20260812; h=from:subject:message-id; bh=+YMNnB36uCYPnvESknZPg1XdNyTz+vgI5gek3fKeo48=; b=jSKrPCAsw2BXuKYpMNOeskFVmpMnqUxTWbEHWJjPIWpHvHt9jgloEqzNa84ob+yNdz9AtOPQm lkPH46r5nu2ChgkZr1RrVm4eJS9jYc9KAKujLBJK5CYXiL6Zt2dsehF X-Developer-Key: i=vasileios.almpanis@virtuozzo.com; a=ed25519; pk=HhuHDXAcPva9A8zccWEMRsb36reu+y4IVbBjXKxNUjw= X-OZ-Fwd: true Cc: devel@openvz.org Subject: Re: [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 > > diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile > index 51f9316beb99..e96320a18384 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 000000000000..5fbf4af49f7f > --- /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); Non blocker: in hosts with many CPUs we could read unflushed stats. Maybe do the memcmp loop first and retry numa_stat read for a few seconds. -- Vasileios Almpanis _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel