All of lore.kernel.org
 help / color / mirror / Atom feed
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


  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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.