Linux Trace Kernel
 help / color / mirror / Atom feed
From: "David Hildenbrand (Arm)" <david@kernel.org>
To: "Gregory Price (Meta)" <gourry@gourry.net>, linux-mm@kvack.org
Cc: linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
	linux-rt-devel@lists.linux.dev, kernel-team@meta.com,
	akpm@linux-foundation.org, mhocko@suse.com, rientjes@google.com,
	shakeel.butt@linux.dev, rostedt@goodmis.org, mhiramat@kernel.org,
	mathieu.desnoyers@efficios.com, muchun.song@linux.dev,
	osalvador@suse.de, ljs@kernel.org, liam@infradead.org,
	vbabka@kernel.org, rppt@kernel.org, surenb@google.com,
	brendan.jackman@linux.dev, hannes@cmpxchg.org, ziy@nvidia.com,
	bigeasy@linutronix.de, clrkwllms@kernel.org,
	mgorman@techsingularity.net, anshuman.khandual@arm.com,
	Bessel.Wang@huawei.com, jiang.liu@huawei.com
Subject: Re: [PATCH v3 0/8] mm: fix allocator races with memory hotplug
Date: Wed, 21 Oct 2026 10:24:48 +0200	[thread overview]
Message-ID: <63521469-3829-4895-a7d9-a0f7806c58a1@kernel.org> (raw)
In-Reply-To: <20261021023940.1940971-1-gourry@gourry.net>

On 10/21/26 04:39, Gregory Price (Meta) wrote:

Usual reminder to slow down a bit :)

We have

	Documentation/process/submitting-patches.rst

Saying

	"Wait for a minimum of one week before resubmitting or pinging reviewers
	 - possibly longer during busy times like merge windows.

A lot of this here was broken forever, so there is no need to rush.

(thanks for the fixes ;) )

> Eight fixes for allocator paths that can act on state another CPU is
> updating: zonelists rebuilt by memory hotplug, and the pagesets of a
> zone that offlining empties.
> 
> I've packaged these together because they're all related to hotplug.
> 
> *** Backport note - Patches 5-7 build on patch 1: without it, their
>     retry is compiled out when CONFIG_MEMORY_HOTREMOVE=n.
> 
> Most of these race conditions stem from zonelists being re-ordered
> in-place at runtime during hotplug.  The result is a mixture of failed
> allocations, NULL dereferences, incorrect placement (GFP_KERNEL in
> ZONE_MOVABLE), node misplacement, or incorrectly allowing hugetlb /
> contig allocations on a node/zone otherwise not eligible.
> 
> Most of these are incredibly narrow race conditions, but since the most
> likely impacted machines are VMs with memory hotplug, the narrow
> condition can more easily occur when a vCPU takes a long interrupt / VM
> exit of some kind.  I've made all of these race conditions
> deterministics with a kprobe reproduction module described below.
> 
> Reproducers
> ===========
> 
> All eight races were reproduced on unmodified source in a QEMU guest
> with three NUMA nodes:
>    node 0: 3G, CPUs 0-1
>    node 1: 1G ZONE_NORMAL, CPU 2
>    node 2: 1G ZONE_NORMAL, CPU 3
> 
> And a 512M pc-dimm cold-plugged on node 1, left offline at boot
> (CONFIG_ACPI_HOTPLUG_MEMORY=y).
> 
> Patch 1 needs CONFIG_MEMORY_HOTREMOVE=n to show the failure
> Patch 3 needs mm/mmzone.o built with -fno-inline -fno-ipa-sra,
>         without which the two reads are merged.
> Patch 4 needs the reclaim_retry_zone tracepoint enabled.
> Patch 7's hugetlb case needs a 64G guest (node 1: 4G) so that one
>         memory block is 1G, with a 2G pc-dimm on node 1.
> 
> The windows are a few instructions wide, so I used a test module to
> widen them with kprobes that stall one CPU inside the window. This
> emulates an interrupt or vCPU preemption.  The vCPU preemption is the
> more likely event to cause this at large scale with memory hotplug.
> 
> Patch 1 (zonelist):
> 
>   Online node 1's first memory block as ZONE_MOVABLE, which rebuilds
>   node 1's zonelists.
> 
>   Stall the rebuild after it writes ZONE_MOVABLE into the first entry
>   but before it writes ZONE_NORMAL into the second.
> 
>   On another CPU, allocate GFP_KERNEL | __GFP_THISNODE from node 1.
> 
>   Before: the allocation fails, 3/3 runs.
>   After:  the allocation waits for the rebuild and succeeds, 3/3 runs.
> 
> Patch 2 (preferred zone):
> 
>   Online node 1's block as ZONE_MOVABLE, so node 1's local zonelist is
>   [MOVABLE, NORMAL].
> 
>   On another CPU, allocate GFP_KERNEL | __GFP_THISNODE from node 1,
>   whose preferred slot is NORMAL, and stall it after it loads the zone.
> 
>   Offline the block, which rebuilds the list as [NORMAL] and moves the
>   terminator into the preferred slot.
> 
>   Before: NULL dereference in get_page_from_freelist(), 3/3 runs.
>   After:  the allocation succeeds from ZONE_NORMAL, 3/3 runs.
> 
> Patch 3 (slot reread):
> 
>   Online node 1's block as ZONE_MOVABLE, so node 1's local zonelist is
>   [MOVABLE, NORMAL].
> 
>   On another CPU, allocate GFP_KERNEL | __GFP_THISNODE from node 1
>   with nodemask {1}, and stall it after __next_zones_zonelist() tests
>   the NORMAL slot for NULL but before it reads the slot again.
> 
>   Offline the block, which rebuilds the list as [NORMAL] and moves the
>   terminator into that slot.
> 
>   Before: NULL dereference in zone_to_nid(), 3/3 runs.
>   After:  the allocation succeeds from ZONE_NORMAL, 3/3 runs.
> 
> Patch 4 (tracepoint):
> 
>   Online node 1's block as ZONE_MOVABLE, so node 1's local zonelist is
>   [MOVABLE, NORMAL], and enable the reclaim_retry_zone tracepoint.
> 
>   On another CPU, allocate GFP_KERNEL | __GFP_THISNODE from node 1, with
>   its get_page_from_freelist() calls failed so that the slowpath reaches
>   should_reclaim_retry(), and stall it in zone_reclaimable_pages() for
>   node 1's NORMAL zone.
> 
>   Offline the block, which rebuilds the list as [NORMAL] and moves the
>   terminator into that slot.
> 
>   Before: NULL dereference in the tracepoint, 3/3 runs. With the
>           tracepoint disabled, no oops (1 run).
>   After:  the allocation succeeds from ZONE_NORMAL, 3/3 runs.
> 
> Patch 5 (zonelist slots):
> 
>   On one CPU, allocate GFP_KERNEL with a nodemask, and stall it after
>   the iterator approves its first zonelist slot but before the
>   allocator loads the zone from that slot.
> 
>   Online node 1's first memory block as ZONE_MOVABLE, which inserts
>   ZONE_MOVABLE at the front of node 1's zonelists.
> 
>   This was run for single-page and bulk allocations, and for a
>   single-page allocation whose fast path is made to fail so that the
>   slowpath's walk races.
> 
>   Before: nodemask {1} gets a node 1 ZONE_MOVABLE page and nodemask
>           {2} a node 1 ZONE_NORMAL page, 18/18 runs.
>   After:  nodemask {1} gets node 1 ZONE_NORMAL and nodemask {2} gets
>           node 2 ZONE_NORMAL, 18/18 runs.
> 
>   The check adds 5 instructions to an order-0 allocation on x86,
>   counted with a hardware counter on a non-debug build.
> 
> Patch 6 (hugetlb):
> 
>   Reserve hugetlb pages on every node. On CPU 1, fault a hugetlb page
>   bound to node 2 with mbind(); the pool walk uses node 0's zonelist,
>   where node 2 follows node 1. Stall the walk after it approves
>   node 2's slot.
> 
>   Online node 1's first memory block as ZONE_MOVABLE, which shifts
>   node 1's ZONE_NORMAL into that slot.
> 
>   Before: the page comes from node 1, 3/3 runs.
>   After:  the page comes from node 2, 3/3 runs.
> 
> Patch 7 (alloc_contig):
> 
>   Write 1 to node 1's 1G nr_hugepages, and stall the gigantic page's
>   zonelist walk after it approves node 1's ZONE_NORMAL slot.
> 
>   Online node 1's first memory block as ZONE_MOVABLE, which inserts
>   ZONE_MOVABLE into that slot.
> 
>   Before: the 1G page is allocated from ZONE_MOVABLE, 3/3 runs.
>   After:  the 1G page is allocated from ZONE_NORMAL, 3/3 runs.
> 
>   Calling alloc_contig_pages() directly with GFP_KERNEL in the first
>   guest, as in patch 5, nodemask {1} gets a ZONE_MOVABLE range and
>   nodemask {2} a node 1 range before, 3/3 runs each, and neither after.
> 
> Patch 8 (pagesets):
> 
>   Online node 1's block as ZONE_MOVABLE, then offline it.
> 
>   Stall the offline before the zone empties, and stall the vmstat
>   worker on another CPU (kicked through /proc/sys/vm/stat_refresh) in
>   decay_pcp_high() for that zone. Let the offline free the pagesets,
>   reallocate the freed percpu area with a known value, then release
>   the worker.
> 
>   Before: decay_pcp_high() runs on the freed pageset, 6/6 runs. In
>           1/6 the area was reallocated before the worker resumed, and
>           decay_pcp_high() rewrote the new object.
>   After:  the pageset is not freed, 3/3 runs.
> 
> All eight reproducers also pass on the series tip, 3/3 runs each.
> 
> Changes in v3
> =============
> - Replaced v2 patch 2 (zonelist_zone_valid() recheck) with patch 5,
>   which checks zonelist_update_seq whenever an allocation succeeds
>   (Vlastimil). A raced attempt's page is discarded, so a stale preferred
>   zone can no longer affect the result (Sashiko).
> - Moved the NULL dereference fixes ahead of patch 5.
> - Added patches 6 and 7 for the hugetlb pool walk and
>   alloc_contig_pages(), which have the same slot shift.
> - Added patch 4 for the reclaim_retry_zone tracepoint's re-read.
> - Fixes: tags on patches 2-6 now name the stop_machine
>   removal that made rebuilds run concurrently with zonelist walks.
> 
> Link: https://lore.kernel.org/linux-mm/20261014222456.2318284-1-gourry@gourry.net/
> Link: https://lore.kernel.org/linux-mm/20261015210539.3243563-1-gourry@gourry.net/
> Link: https://lore.kernel.org/linux-mm/20261019172346.3344005-1-gourry@gourry.net/
> 
> Gregory Price (Meta) (8):
>   mm/page_alloc: detect zonelist rebuilds from memory hot-add
>   mm/page_alloc: snapshot the preferred zone against shrink rebuilds
>   mm/mmzone: read each zoneref slot once when filtering by nodemask
>   mm/page_alloc: pass the zone, not the zoneref, to reclaim_retry_zone
>   mm/page_alloc: retry allocations that raced with a zonelist rebuild
>   mm/hugetlb: retry the pool walk when the zonelist is rebuilt
>   mm/page_alloc: retry alloc_contig_pages() when the zonelist is rebuilt
>   mm/page_alloc: keep a zone's pagesets when it goes empty
> 
>  include/trace/events/oom.h |   8 +--
>  mm/hugetlb.c               |  13 +++--
>  mm/mmzone.c                |  11 ++--
>  mm/page_alloc.c            | 106 +++++++++++++++++++++++++++++--------
>  mm/page_alloc.h            |   3 ++
>  5 files changed, 107 insertions(+), 34 deletions(-)
> 


-- 
Cheers,

David

  parent reply	other threads:[~2026-10-21  8:24 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-21  2:39 [PATCH v3 0/8] mm: fix allocator races with memory hotplug Gregory Price (Meta)
2026-10-21  2:39 ` [PATCH v3 1/8] mm/page_alloc: detect zonelist rebuilds from memory hot-add Gregory Price (Meta)
2026-10-21  2:39 ` [PATCH v3 2/8] mm/page_alloc: snapshot the preferred zone against shrink rebuilds Gregory Price (Meta)
2026-10-21  2:39 ` [PATCH v3 3/8] mm/mmzone: read each zoneref slot once when filtering by nodemask Gregory Price (Meta)
2026-10-21  2:39 ` [PATCH v3 4/8] mm/page_alloc: pass the zone, not the zoneref, to reclaim_retry_zone Gregory Price (Meta)
2026-10-21  2:39 ` [PATCH v3 5/8] mm/page_alloc: retry allocations that raced with a zonelist rebuild Gregory Price (Meta)
2026-10-21  2:39 ` [PATCH v3 6/8] mm/hugetlb: retry the pool walk when the zonelist is rebuilt Gregory Price (Meta)
2026-10-21  2:39 ` [PATCH v3 7/8] mm/page_alloc: retry alloc_contig_pages() " Gregory Price (Meta)
2026-10-21  2:39 ` [PATCH v3 8/8] mm/page_alloc: keep a zone's pagesets when it goes empty Gregory Price (Meta)
2026-10-21  8:24 ` David Hildenbrand (Arm) [this message]
2026-10-21 13:24   ` [PATCH v3 0/8] mm: fix allocator races with memory hotplug Gregory Price (Meta)

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=63521469-3829-4895-a7d9-a0f7806c58a1@kernel.org \
    --to=david@kernel.org \
    --cc=Bessel.Wang@huawei.com \
    --cc=akpm@linux-foundation.org \
    --cc=anshuman.khandual@arm.com \
    --cc=bigeasy@linutronix.de \
    --cc=brendan.jackman@linux.dev \
    --cc=clrkwllms@kernel.org \
    --cc=gourry@gourry.net \
    --cc=hannes@cmpxchg.org \
    --cc=jiang.liu@huawei.com \
    --cc=kernel-team@meta.com \
    --cc=liam@infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=linux-rt-devel@lists.linux.dev \
    --cc=linux-trace-kernel@vger.kernel.org \
    --cc=ljs@kernel.org \
    --cc=mathieu.desnoyers@efficios.com \
    --cc=mgorman@techsingularity.net \
    --cc=mhiramat@kernel.org \
    --cc=mhocko@suse.com \
    --cc=muchun.song@linux.dev \
    --cc=osalvador@suse.de \
    --cc=rientjes@google.com \
    --cc=rostedt@goodmis.org \
    --cc=rppt@kernel.org \
    --cc=shakeel.butt@linux.dev \
    --cc=surenb@google.com \
    --cc=vbabka@kernel.org \
    --cc=ziy@nvidia.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox