From: sashiko-bot@kernel.org
To: "Qi Zheng" <qi.zheng@linux.dev>,
muchun.song@linux.dev, osalvador@suse.de, david@kernel.org,
akpm@linux-foundation.org, azpijr@gmail.com,
lance.yang@linux.dev
Cc: linux-mm@kvack.org, "Qi Zheng" <zhengqi.arch@bytedance.com>
Subject: Re: [PATCH v2 4/4] mm: hugetlb: fix cpuset violations in gigantic surplus allocations
Date: Thu, 05 Nov 2026 05:15:01 +0000 [thread overview]
Message-ID: <sashiko-outbox-198588@kernel.org> (raw)
In-Reply-To: <046fd77e49ee8bb35e3bcc4104b9275d2e72ea5e.1793850539.git.zhengqi.arch@bytedance.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] mm: hugetlb: empty nodemask passed in alloc_buddy_hugetlb_folio()
- [Medium] mm: hugetlb: MPOL_BIND violation in alloc_buddy_hugetlb_folio() on cpuset race
--
commit 6e27dfcfc4f6238bea6b6e8639c15188d8d94295
Author: Qi Zheng <qi.zheng@linux.dev>
mm: hugetlb: fix cpuset violations in gigantic surplus allocations
This commit intersects the allocation nodemask in alloc_buddy_hugetlb_folio()
with cpuset_current_mems_allowed so gigantic surplus hugepage allocations do
not allocate from nodes outside the current task's cpuset. It also retries
failed allocations if cpuset_current_mems_allowed changes during the attempt.
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index 6fba92b0f6749..09b64f7c11dd2 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -2181,22 +2181,33 @@ struct folio *alloc_buddy_hugetlb_folio(struct hstate *h,
> gfp_t gfp_mask, struct mempolicy_interpreted *mpoli)
> {
> struct folio *folio = NULL;
> - nodemask_t *nodemask = mpoli->nodemask;
> + nodemask_t alloc_nodemask;
> + unsigned int cpuset_mems_cookie;
> +
> +retry:
> + cpuset_mems_cookie = read_mems_allowed_begin();
> + alloc_nodemask = cpuset_current_mems_allowed;
> + if (mpoli->mode == MPOL_BIND && mpoli->nodemask &&
> + nodes_intersects(*mpoli->nodemask, alloc_nodemask))
> + nodes_and(alloc_nodemask, alloc_nodemask, *mpoli->nodemask);
>
> if (mpoli->mode == MPOL_PREFERRED_MANY) {
> gfp_t gfp = gfp_mask & ~(__GFP_DIRECT_RECLAIM | __GFP_NOFAIL);
>
> + nodes_and(alloc_nodemask, alloc_nodemask, *mpoli->nodemask);
> folio = alloc_surplus_hugetlb_folio(h, gfp, mpoli->nid,
> - nodemask);
> + &alloc_nodemask);
[Severity: Medium]
What happens here when an MPOL_PREFERRED_MANY policy's nodemask is disjoint
from cpuset_current_mems_allowed (for example, after moving a task or
non-leader thread to a cpuset on different NUMA nodes, since
mpol_rebind_preferred() does not remap pol->nodes)?
Unlike the MPOL_BIND branch above, this path does not check whether the
intersection is non-empty before calling alloc_surplus_hugetlb_folio() with
&alloc_nodemask. When alloc_nodemask is empty:
1. For gigantic hugepages, alloc_surplus_hugetlb_folio() performs a wasted
pass through CMA and alloc_contig_frozen_pages() with an empty nodemask.
2. For non-gigantic hugepages, alloc_surplus_hugetlb_folio() ->
alloc_fresh_hugetlb_folio() -> only_alloc_fresh_hugetlb_folio() ->
alloc_buddy_frozen_folio() -> __alloc_frozen_pages_noprof() fails the fast
path and enters __alloc_pages_slowpath(). In check_retry_cpuset():
mm/page_alloc.c:check_retry_cpuset() {
if (cpusets_enabled() && ac->nodemask &&
!cpuset_nodemask_valid_mems_allowed(ac->nodemask)) {
ac->nodemask = NULL;
return true;
}
}
Because cpuset_nodemask_valid_mems_allowed() returns false for an empty
nodemask, check_retry_cpuset() treats it as a raced MPOL_BIND nodemask,
resets ac->nodemask to NULL, and restarts __alloc_pages_slowpath() with
__GFP_DIRECT_RECLAIM masked off. That wakes kswapd across all nodes via
wake_all_kswapds() with ac->nodemask == NULL, attempts an allocation on
non-preferred fallback cpuset nodes at ALLOC_WMARK_MIN before the normal
fallback pass at ALLOC_WMARK_LOW with reclaim/compaction runs, and
increments HTLB_BUDDY_PGALLOC_FAIL in alloc_buddy_frozen_folio() if that
first pass fails.
Should the first-pass alloc_surplus_hugetlb_folio() call be guarded by
if (nodes_and(alloc_nodemask, alloc_nodemask, *mpoli->nodemask)) so that an
empty intersection skips straight to the cpuset fallback pass?
>
> - /* Fallback to all nodes if page==NULL */
> - nodemask = NULL;
> + /* Fallback to all nodes allowed by the current cpuset */
> + alloc_nodemask = cpuset_current_mems_allowed;
> }
>
> - if (!folio) {
> + if (!folio)
> folio = alloc_surplus_hugetlb_folio(h, gfp_mask, mpoli->nid,
> - nodemask);
> - }
> + &alloc_nodemask);
> +
> + if (!folio && unlikely(read_mems_allowed_retry(cpuset_mems_cookie)))
> + goto retry;
[Severity: Medium]
Can pre-intersecting alloc_nodemask with cpuset_current_mems_allowed cause
non-gigantic surplus allocations to violate MPOL_BIND or use a stale nodemask
when cpuset_current_mems_allowed changes concurrently?
alloc_buddy_hugetlb_folio() is used for both gigantic and non-gigantic
surplus hugepage allocations. For non-gigantic hugepages,
alloc_surplus_hugetlb_folio() calls into __alloc_frozen_pages_noprof() ->
__alloc_pages_slowpath() -> check_retry_cpuset(), which expects ac->nodemask
to be the raw policy nodemask rather than a nodemask pre-intersected with an
earlier snapshot of cpuset_current_mems_allowed:
1. Suppose a VMA MPOL_BIND policy has *mpoli->nodemask = {0, 2} and
current->mems_allowed is initially {0}, so alloc_nodemask is computed as
{0}. If current->mems_allowed changes to {1, 2} while the thread enters
__alloc_pages_slowpath(), check_retry_cpuset() sees that ac->nodemask ({0})
no longer intersects current->mems_allowed ({1, 2}), even though the raw
MPOL_BIND mask ({0, 2}) still intersects {1, 2} on node 2.
check_retry_cpuset() then clears ac->nodemask = NULL and restarts the
slowpath, which can allocate a folio on node 1 (allowed by the new cpuset
{1, 2}, but forbidden by MPOL_BIND {0, 2}). Because folio is non-NULL,
!folio && unlikely(read_mems_allowed_retry(cpuset_mems_cookie)) does not
trigger a retry, and the folio from node 1 is returned.
2. In the MPOL_BIND check at the top of alloc_buddy_hugetlb_folio():
if (mpoli->mode == MPOL_BIND && mpoli->nodemask &&
nodes_intersects(*mpoli->nodemask, alloc_nodemask))
nodes_and(alloc_nodemask, alloc_nodemask, *mpoli->nodemask);
*mpoli->nodemask is read twice. If mpoli->nodemask points to a task
mempolicy (current->mempolicy->nodes), a concurrent
cpuset_change_task_nodemask() -> mpol_rebind_task() ->
mpol_rebind_nodemask() can update pol->nodes between nodes_intersects()
and nodes_and(), leaving alloc_nodemask empty.
3. For MPOL_DEFAULT (where nodemask was previously NULL), passing
&alloc_nodemask (for example {0, 1}) into __alloc_frozen_pages_noprof()
means that if current->mems_allowed changes to {1, 2} during
__alloc_pages_slowpath(), check_retry_cpuset() sees a non-empty
intersection on node 1 and loops in the slowpath with ac->nodemask still
restricted to {0, 1}, preventing slowpath retries from considering newly
allowed node 2.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1793850539.git.zhengqi.arch@bytedance.com?part=4
next prev parent reply other threads:[~2026-11-05 5:15 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-05 4:07 [PATCH v2 0/4] mm: hugetlb: fix CMA-related issues Qi Zheng
2026-11-05 4:07 ` [PATCH v2 1/4] x86: hugetlb: disable default CMA reservations on 32-bit kernel Qi Zheng
2026-11-05 9:29 ` Oscar Salvador (SUSE)
2026-11-05 4:07 ` [PATCH v2 2/4] mm: gup: allow long-term pins of CMA-allocated HugeTLB folios Qi Zheng
2026-11-05 5:13 ` Muchun Song
2026-11-06 2:48 ` Qi Zheng
2026-11-06 2:54 ` Muchun Song
2026-11-05 9:33 ` Oscar Salvador (SUSE)
2026-11-05 11:28 ` Jose A. Perez de Azpillaga
2026-11-05 4:07 ` [PATCH v2 3/4] mm: hugetlb: fix GFP mask propagation to CMA Qi Zheng
2026-11-05 5:17 ` Muchun Song
2026-11-05 10:00 ` Oscar Salvador (SUSE)
2026-11-06 2:43 ` Qi Zheng
2026-11-06 5:50 ` Oscar Salvador (SUSE)
2026-11-06 6:12 ` Qi Zheng
2026-11-05 11:28 ` Jose A. Perez de Azpillaga
2026-11-05 4:07 ` [PATCH v2 4/4] mm: hugetlb: fix cpuset violations in gigantic surplus allocations Qi Zheng
2026-11-05 5:15 ` sashiko-bot [this message]
2026-11-06 7:43 ` Qi Zheng
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=sashiko-outbox-198588@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=akpm@linux-foundation.org \
--cc=azpijr@gmail.com \
--cc=david@kernel.org \
--cc=lance.yang@linux.dev \
--cc=linux-mm@kvack.org \
--cc=muchun.song@linux.dev \
--cc=osalvador@suse.de \
--cc=qi.zheng@linux.dev \
--cc=sashiko-reviews@lists.linux.dev \
--cc=zhengqi.arch@bytedance.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