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 9EBBA8045D for ; Wed, 28 Oct 2026 23:19:04 +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 69SNHlFm024411; Thu, 29 Oct 2026 02:17:48 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 69SNHlFm024411 Authentication-Results: mail.openvz.org; dkim=fail reason="signature verification failed" (2048-bit key) header.d=virtuozzo.com header.i=@virtuozzo.com header.b="exgEuFI9" Received: from mail-wm1-f70.google.com (mail-wm1-f70.google.com [209.85.128.70]) by mail.openvz.org (8.14.4/8.14.4) with ESMTP id 69SNHcie024387 (version=TLSv1/SSLv3 cipher=AES128-GCM-SHA256 bits=128 verify=FAIL) for ; Thu, 29 Oct 2026 02:17:39 +0300 DKIM-Filter: OpenDKIM Filter v2.11.0 mail.openvz.org 69SNHcie024387 Received: by mail-wm1-f70.google.com with SMTP id 5b1f17b1804b1-4a1fa972b4fso7447435e9.2 for ; Wed, 28 Oct 2026 16:17:39 -0700 (PDT) X-Forwarded-Encrypted: i=2; AKwUvByc5xX+/KKtr9+hlBccP3SiyxTzqNQTIMJxMekd1kb36DI8aFlhI+t+Shr9W929c35pRh0ROg==@openvz.org X-Gm-Message-State: AFq9FYIPAjCM6oOaVYpkxhzkWryJa8DrRy4AATL0x2EdXfF6TVNb9jLO 3go1O5w1FLk4B6xECAeW2SfDKkfUcsMtNIfN9tAsvWcohKkzuoxThbtqZzLPB+D7TF9b8bxBThJ YTKVqs7DhejjH5TtMMT1Ar23VGLVz/DmBoq9vpxz59HBD0vAxE353ww== X-Gm-Gg: AYBFou3ghPLeOWpsZNRUFV3y/7ewinQ/EJCAJVOa5PXYFjHsKlJtb76CeT2TD5m0MrO avRDu/YfHUESLCz9Xj4n9c6j+BWh/tQvRb+2xAehI9Z97JN2IJBiRk+NxszCgmi6L/m5O1q/v0Z r6b/S5sySFhi2k9f8OMEpUNes4axWnd4yzYKnhP9ukERAiBgR0bk8efAar/Z0JaTramzhefl6G+ 0rJqiVXigZNUwUCM1Va45UWDCHqd7mvU0H/gCMwKBvsEF6qEli4o9ZI6ISJekKffdMrw5d7nmcD eO8eT/BCDiXyaVnUaGnBHqobeJDjsr1ir6o9mZuXwsZv/rt68HUdnR481xZb5Kt5uoD5WzTF6fx yPzsuO88Q2A7vTio5Bw== X-Received: by 2002:a05:600c:4455:b0:4a2:148b:c88c with SMTP id 5b1f17b1804b1-4a965be61f9mr78651485e9.8.1793229457381; Wed, 28 Oct 2026 16:17:37 -0700 (PDT) X-Received: by 2002:a05:600c:4455:b0:4a2:148b:c88c with SMTP id 5b1f17b1804b1-4a965be61f9mr78651155e9.8.1793229456811; Wed, 28 Oct 2026 16:17:36 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1793229456; cv=none; d=google.com; s=arc-20260327; b=CJNOzmoUL/MPjbsAILC353Q49Q7DxK6VnM/uYuwyP41IcgmWG704THbvMxUoYl6fKe JIK68tNyxr1H0PJ+yj2AQXtwM9eBYc0K3j2G74in0FDnVnalKFBkSie/nOCdyM5lP3h2 Rsvfdc/FQgwR1aDg6YLBCWZMZvqUtzmbPnCIU7fdDGBlkOScWk7w4Yqm06D703bhonm+ WQJCiTZTLGZmYV9Sdx4BiZbxANQUBIfrzB4ZUOZIEQKu610B5aDGwOzuIZyVIvyOSBHz a98Jn/63kG6NhQlTUbt3kX1B6WYn9e1ffLjXcorso1MCUfTh9RacHkOn347Zw65bJs2v LHBw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:content-transfer-encoding:mime-version:message-id:date :subject:from:dkim-signature; bh=8SU7YbK7Jnt1Cre4P9g8OkQRt+93S7SgtcrZQrkSm2U=; fh=XcqC0Cib/C4XsQyLUc3b6E9Ii37Wk0LS3yBNHj5szHI=; b=SN55oAGUruX+4iUWtKtsJo/fw3Xg5hT0t122IoiUvmzGF7vMSEiLYdw35fAcUwa8kH 4lx0fRlBcXsRLaScrM979auQIGWu1eXLdZNBw8D5GnQ3SdVTC0m0IEXJ7WX3kzfu2FIZ j2oTzlDgTwwiJjehrGigbhtGxWwlk/dOl8i/RRjimjSzDXlR7OAm7iLWi8dRZMUgNMIh H3U4VGkraG0a0HltyoU6iN9t6k/fhpGeTwwZBQaj0db3W3cGfD3s3+/p/QmYxKW7DqtL DuO+eKvnHJi3VGCDCCfOBtM7Ww+puSiT7nrZZT4tdxft8atOCw1ZnS4mmeAhKFEfXKEJ aflw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@virtuozzo.com header.s=relay header.b=exgEuFI9; 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 5b1f17b1804b1-4aa81d6a00fsi18640175e9.7.2026.10.28.16.17.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 28 Oct 2026 16:17:36 -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=exgEuFI9; 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=Content-Type:MIME-Version:Message-Id:Date:Subject :From; bh=8SU7YbK7Jnt1Cre4P9g8OkQRt+93S7SgtcrZQrkSm2U=; b=exgEuFI9XmQrd5GLfsy 0ryQhJEKRkzFqsiZ/moRQsRnjd9du/hobwJ7azu2VCYZouZJilTgG3xlPkOQ2JnkG/ib/3sry6awV gnClWEB1HrXs6KihPCni9h8NwVojjqhKtL0+K06guGLWzS2dVYojZ8TckOXYYF3wrqIKrDXU08bt9 txFrm9peP5RHljtpEedl03BQ/6D/IzAk4A/Dv1eo00nSJDnZ2JFIOlOSKpHcH6wi/LREL9qIUskLf 5jntPPmYtSiZce+nFeP2rg06liNArOe2Xxo3B2rkgf+YjTPWONAOJ3dr74W2xZ00N5OwXl84qDJi8 R4cUTdVwSyqY1eQ==; 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 1xMCpx-004Nh2-0U; Thu, 29 Oct 2026 00:17:34 +0100 From: Konstantin Khorenko Date: Thu, 29 Oct 2026 00:17:30 +0100 Message-Id: <20261029-b4-prep-khorenko-vstor-151607-numa-migrate-order-v4-0-cf966bf93405@virtuozzo.com> MIME-Version: 1.0 X-B4-Tracking: v=1; b=H4sIAIqC4moC/6XPS27DIBAA0KtErDuInzHuqveougA8jWlkE4GNU ke+e7Gzq7pqdvNheDN3kjEFzOT1dCcJS8ghTjVRLyfiBzudEUJfcyKY0JwJA07BNeEVLkNMOF0 ilDzHBLzhmrUwLaOFMZyTnRFi6jGBQmm1VtZ0vSL12zr9GW4H+U4GH8BQTj4ejby4L/TzDu5Pn c0ILtnJD3vpEUEaOANNuaAMBOe0EZRTIeiNlrV2Yln32SHse30fhxV+YP+/oXColjZS6kZo3bZ vJaR5iesaqY/jsX4RzyKiIq5rpTXCNo75vxD5LCIrYjqpOoWmd63+jWzb9gNEiNxjFQIAAA== X-Change-ID: 20261028-b4-prep-khorenko-vstor-151607-numa-migrate-order-4e3a664a89d4 To: Pavel Tikhomirov X-Mailer: b4 0.16.0 X-Developer-Signature: v=1; a=openpgp-sha256; l=10829; i=khorenko@virtuozzo.com; h=from:subject:message-id; bh=dNZ0mA4VpUugCKS5gtn/lTOVYOvnAwqqUF8zmBLWSPE=; b=owEBbQKS/ZANAwAKAVGWCkf5YVwqAcsmYgBq4oKOz580SDZqzTx2GZYZ5oGiyfQ9am3vfIFa3 /ynDt6gE/eJAjMEAAEKAB0WIQRWD23IPcXT0GO9CCxRlgpH+WFcKgUCauKCjgAKCRBRlgpH+WFc KuYHD/90zI59gZ8fUGprxMTYgHFHMYtGC5T2xeCcQJM5XddaK2lDUf4NvaQCRwxZVVMvi0A5Msc ExbsrY6gdMQoECgO8sV4e21AY4VyRKz5vrkDJWWQ3ZlmyOB94+SWxEDUphqbKiC66z4NWLKXiqf BI5Tn6eogjxMqiqvaslNkq1QEXCVjX4LsdlSYuVXH2m1/QE/5ol5UHJI2tYKC4E4aHAyWnQQ3tO J17diOVQUaN449b/t4ESKNWH0jxQh+HKCOIRFX1W1qYV+KuJUTkCdSeFGb5ZrbCMfNXVwO0/A4L 4Bf89h8C77EmmU/fjeNzdo+4yNgpsCarUcyYHKuI+kVQLp0daWmV1PYVkBzPyui6GeG9f7iEFvR ewKenOnfPhGE1jAp7hWpI5dZ4ic2cPi9h+uJpqB4zD3x/U5a9MZnryHT+6NY56n3jQl7oNOkw4O 7jMURpipC7nURGiCgzxDoFqMSO6fUPe9cDALoagP9GDKfIQAOD7VlaocEeG405hLsv4yYNhgMPm 7JO5oHbXy+iN6pZUI0r4U3I8TW1XvOECWHxuBwGMzJlwZmtlXQcaWQMBGlW9xy6I/PrzotniNEe Jt8IEnf6feVHjbtDGNIP1gupS8RVgZU8E3B+pTQ/uKdH2irzSN1ySloHR5xahMLXU2aICBvK2Er da63xwG8mVPkMNA== 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 v4 0/2] mm/memcontrol: numa_migrate destinations of the source 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 memory.numa_migrate, our memcg file that vcmmd uses to move a cgroup's memory between NUMA nodes, allocates a 2 MB destination for every large folio it migrates. Page cache folios of a smaller order get a destination of the wrong size, and the page cache falls apart around it. Patch 1 is the fix, patch 2 a selftest that catches it. What the customer hit ===================== A two-node VMware host runs VMs with vcmmd's NUMA policy enabled, so every few minutes vcmmd writes memory.numa_migrate for a VM's cgroup. qemu writes its backup scratch file on NFS, and NFS sizes each page cache folio to the write, so that file is full of order-1 to order-5 folios. Closing the scratch file dies in nfs4_evict_inode(): kernel BUG at fs/inode.c:737! RIP: 0010:clear_inode+0x75/0x80 mapping->nrpages of the inode is negative, and the vmcore holds order-9 folios of that file with an index not aligned to 512, the refcount of a small folio, and whole xarray ranges of live folios that are no longer in the tree. Reads of the migrated ranges return garbage, so the backups taken on that host while the policy was active should be verified. The same crash was filed as VSTOR-151676. Where the 2 MB comes from ========================= memcg_numa_migrate_new_folio() is the migrate_pages() allocation callback. Its large folio branch dates from vz8 (4.18) and follows what new_page() in mm/mempolicy.c did on the 4.x kernels: if (thp_migration_supported() && PageTransHuge(page)) alloc ... HPAGE_PMD_ORDER A compound page on the LRU could only be a PMD-sized THP then, so the test implied the order. Upstream replaced that with folio_order() of the source when its allocators moved to folios, ffe06786b540 ("mm/migrate: Use a folio in alloc_migration_target()"). Our copy kept the shape, only PageTransHuge() became folio_test_large(), which is folio_test_head() and is true for any order from 1 up. NFS and XFS large folios, shmem and anonymous mTHP now all reach it. Why an oversized destination breaks the page cache ================================================== The two sides of the page cache size the folio differently. On insertion __folio_migrate_mapping() takes everything from the source: nr = folio_nr_pages(old), the xarray state at the old index with no xas_set_order(), newfolio->index = old->index, folio_ref_add(newfolio, nr), and xas_store() replaces exactly the slots the source occupied. folio_copy() copies the source pages only. So a 2 MB folio enters the cache as, say, a 4-page one: 4 xarray slots, 4 extra references, nrpages unchanged, 4 pages of valid data. Everything afterwards asks the folio itself, and it answers order 9: - page_cache_delete(): xas_set_order(index, 9) and nrpages -= 512. 512 slots wiped instead of 4, with the index rounded down to 512 so that other live folios lose their slots, and 512 subtracted instead of 4. That is the negative nrpages and the orphaned folios. - __remove_mapping(): expects 1 + 512 references and finds 1 + 4. Reclaim can never free the folio. - filemap_free_folio(): drops 512 references from a handful at eviction. The refcount goes negative and the folio leaks. - reads: folio_file_page() picks the page by its offset inside the folio, but only the first pages were copied. The data damage shows on the first read of the folio: filemap_read() takes the page at ki_pos & (folio_size - 1) of the destination, which is uninitialised unless the index is 2 MB aligned, and a longer read that starts there continues through the destination past the neighbours. The structural damage waits for the first operation that applies folio_nr_pages() or folio_order() to it: invalidation, truncate, reclaim, inode eviction. On NFS cache invalidation is routine (an attribute change), so the bug shows up there instead of staying a silent 2 MB leak per folio. In short: inserted by the source size, removed by the destination size. That only works when the two match, which for a real THP they always did. A debug kernel says the same on the first migration, in mem_cgroup_migrate(): VM_BUG_ON_FOLIO(folio_nr_pages(old) != folio_nr_pages(new)) The patches =========== Patch 1 allocates the destination with folio_order() of the source, GFP_TRANSHUGE_LIGHT for large folios as before. The thp_migration_supported() test goes: migrate_pages() splits PMD-mappable folios itself when THP migration is unsupported, and every other large folio still needs a matching destination. Patch 2 adds tools/testing/selftests/ve/ve_numa_migrate_test.c. It binds a cgroup to one NUMA node, writes a file on a loop-mounted XFS image in 64K chunks so that the page cache holds large folios, widens cpuset.mems to both nodes (the destination allocation honours the writer's cpuset), migrates the cgroup to the other node through memory.numa_migrate and compares what reads back. The chunks at 2 MB aligned offsets are left out: a folio there reads right even from an oversized destination. memory.current cannot serve as the detector: mem_cgroup_migrate() only transfers the charge, whatever the size mismatch. On an unfixed kernel the comparison fails. The folio sizes are not taken on faith: the test records, page by page, the folio order through /proc/self/pagemap and /proc/kpageflags and the node through move_pages() in query mode, before and after the migration. The file must hold a large folio below the PMD size before (PMD-sized folios got a correct destination from the old code too). After, every such folio must have kept its order on the target node, been split into base pages or stayed behind, which migrate_pages() may do under memory pressure, and at least one must have kept its order. A failed check is a failure, not a skip: a skip would hide that the test no longer reaches the bug. memory.numa_stat is only logged, polled for a few seconds since memcg flushes its per-cpu counters lazily. It needs root, two NUMA nodes with memory (numa=fake=2 is enough), cgroup2 with memory and cpuset, and mkfs.xfs; it skips otherwise. Testing ======= On a VM booted with numa=fake=2: - the reproducer (64K and 8K writes on XFS from a node-bound cgroup, memory.numa_migrate to the other node, unlink) kills the unfixed debug kernel on the VM_BUG_ON above and the release kernel in clear_inode(); with patch 1 migration succeeds and unlink and eviction complete; - the selftest passes on the fixed kernel and fails on the comparison on 22.11. Other branches ============== The same patch applies as is to the 20.x branch (the customer's kernel); the function is a plain callback, so it is a ReadyKernel candidate. vz9 carries the same PageTransHuge() code, but the file systems we ship on 5.14 (XFS, NFS, ext4, shmem) put no non-PMD large folios on the LRU, so it is not reachable there in practice. https://virtuozzo.atlassian.net/browse/VSTOR-151607 Assisted-by: LLM Signed-off-by: Konstantin Khorenko --- Changes in v4: - Patch 2: the folios are compared one by one before and after the migration, so a destination of another order is caught directly; the log says how many kept their order, were split or stayed behind (Sashiko). - Patch 2: the memory.numa_stat poll waits for the bytes actually written, not FILE_SIZE, which the holes made unreachable (Sashiko). - Patch 2: memory.numa_migrate is written with a scan limit, so the migration does not depend on the lazily flushed LRU stats on hosts with the classic LRU; a negative shift in compare_folios() for the holes, a missing bound in its page loop (independent review). - Patch 2: the commit message says that the read-back catches the bug and the folio checks prove the large folio path ran, and why cpuset.mems is widened (independent review). - Patch 2: the failure messages give the errno of a failed memory.numa_migrate write, the offset of a chunk that reads back wrong and the likely cause of a folio check failure; the binary is added to .gitignore (independent review). - Patch 2: the test no longer tries to spare the node on an unfixed kernel; what happens after a failure is left to whoever debugs it. - Link to v3: http://lore.virtuozzo.com/20261028-b4-prep-khorenko-vstor-151607-numa-migrate-order-v3-0-893494e8db76@virtuozzo.com Changes in v3: - Patch 2: the large folio guard requires a folio below the PMD size and the page size comes from sysconf() (Sashiko). After the migration the test checks through move_pages() that at least one such folio sits on the target node in one piece, instead of requiring the whole file in memory.numa_stat, which migrate_pages() does not guarantee (Sashiko); the stat is logged only. - Patch 2: the chunks at 2 MB aligned offsets are not written, so an oversized destination always shows in the read-back, and the image stays mounted until the last check passes; the folio sizes per node are logged before and after the migration; the mkfs.xfs check goes, a failing mkfs skips the test anyway (independent review). - Patch 2: Reviewed-by tags dropped, the test changed too much since the review. - Link to v2: http://lore.virtuozzo.com/20261028-b4-prep-khorenko-vstor-151607-numa-migrate-order-v2-0-b973a82a5b0c@virtuozzo.com Changes in v2: - Patch 1: the commit message says why the thp_migration_supported() check goes (Pavel). - Patch 2: the folio sizes are checked through /proc/self/pagemap and /proc/kpageflags before the migration; a file without a large folio fails the test on purpose (Pavel). - Patch 2: the read-back comparison runs first and the memory.numa_stat check after it, polled for a few seconds, since memory_numa_stat_show() flushes the per-cpu counters only past a per-CPU threshold (Vasileios). - Reviewed-by from Pavel and Vasileios on both patches. - Link to v1: http://lore.virtuozzo.com/20261028-b4-prep-khorenko-vstor-151607-numa-migrate-order-v1-0-268336526677@virtuozzo.com --- Konstantin Khorenko (2): mm/memcontrol: allocate numa_migrate targets of the source folio order selftests/ve: check memory.numa_migrate keeps the folio order mm/memcontrol.c | 11 +- tools/testing/selftests/ve/.gitignore | 1 + tools/testing/selftests/ve/Makefile | 1 + tools/testing/selftests/ve/ve_numa_migrate_test.c | 562 ++++++++++++++++++++++ 4 files changed, 570 insertions(+), 5 deletions(-) --- base-commit: b499efa17aa9fb8f20512d1831d1cb61b22e60d3 change-id: 20261028-b4-prep-khorenko-vstor-151607-numa-migrate-order-4e3a664a89d4 Best regards, -- Konstantin Khorenko, Virtuozzo Linux Kernel Team _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel