Re: [PATCH 2/2] mm: page_alloc: remove ALLOC_NON_BLOCK from ALLOC_RESERVES

From: Vlastimil Babka (SUSE)

Date: Tue Sep 22 2026 - 08:12:19 EST


On 9/21/26 4:39 PM, Johannes Weiner wrote:
> 1ebbb21811b7 ("mm/page_alloc: explicitly define how __GFP_HIGH
> non-blocking allocations accesses reserves") stopped handing out
> reserve access for ALLOC_NON_BLOCK on its own: the extra 25% below the
> min watermark is now only granted on top of ALLOC_MIN_RESERVE. But the
> flag was left in ALLOC_RESERVES, which produces something odd:
>
> With the ALLOC_RESERVES match, __zone_watermark_unusable_free() doesn't
> subtract the free highatomic pages for them. So in the slowpath, they
> get to consume regular blocks below the min watermark by the number of
> free highatomic pages. The highatomic reserve is capped at 1% of the
> zone, which on any decently sized machine is a multiple of the min
> watermark: GFP_NOWAIT can drain regular memory to zero.
>
> The user-visible result is brutal hiccups during bursts of GFP_NOWAIT
> allocations under memory pressure. On a 32G box with an anonymous
> working set, swap, and a filled 290M highatomic reserve, a GFP_NOWAIT
> burst drove regular free memory in the 28G Normal zone (min=60M) to
> 28M, 0.8M and 0.6M in three runs. Swapout failed to allocate its swap
> table, reclaim scanned 13M pages to reclaim 200k, page faults stalled
> for tens to hundreds of milliseconds. The machine survives it, but not
> by design: direct reclaimers eventually fail and start unreserving
> highatomic blocks, until the allocation succeeds or the reserve is
> gone and the OOM killer runs. That reserve exists for high-order
> atomic allocations; here it is destroyed to bail out a GFP_NOWAIT
> consumer that was never entitled to the memory.

This suggests to me that a LLM review of 1/2 spotted this issue and also
(or you) constructed a test doing the GFP_NOWAIT bursts to confirm the
impact, but it has not been observed in production? But if it was, can
we make it clear?

> Remove ALLOC_NON_BLOCK from ALLOC_RESERVES. With that, the GFP_NOWAIT
> burst is stopped short at the min watermark. No direct reclaim, no
> stalls, no failed allocations, and the highatomic reserve stays intact
> for the requests it exists for.
>
> __zone_watermark_ok() is unaffected, since everything it keys on
> ALLOC_NON_BLOCK is already nested under ALLOC_MIN_RESERVE. Update the
> flag comments accordingly: ALLOC_NON_BLOCK just means the caller can't
> block; the reserve math belongs with ALLOC_MIN_RESERVE.
>
> Fixes: 1ebbb21811b7 ("mm/page_alloc: explicitly define how __GFP_HIGH non-blocking allocations accesses reserves")
> Cc: stable@xxxxxxxxxxxxxxx
> Assisted-by: LLM
> Signed-off-by: Johannes Weiner <hannes@xxxxxxxxxxx>

The change itself is fine and we don't need ALLOC_NON_BLOCK in
ALLOC_RESERVES. But I wonder if we should also make the
__zone_watermark_unusable_free() check more precise, by using
may_access_highatomic_reserves() there instead of ALLOC_RESERVES?
Which would mean that the function should however also evaluate
ALLOC_HIGHATOMIC, and restrict the other checks to order=0, to be usable
from both callers.

> ---
> mm/page_alloc.h | 11 +++++------
> 1 file changed, 5 insertions(+), 6 deletions(-)
>
> diff --git a/mm/page_alloc.h b/mm/page_alloc.h
> index ad89f83d1dab..c8af79decbd0 100644
> --- a/mm/page_alloc.h
> +++ b/mm/page_alloc.h
> @@ -32,12 +32,11 @@
> #define ALLOC_OOM ALLOC_NO_WATERMARKS
> #endif
>
> -#define ALLOC_NON_BLOCK 0x10 /* Caller cannot block. Allow access
> - * to 25% of the min watermark or
> - * 62.5% if __GFP_HIGH is set.
> - */
> +#define ALLOC_NON_BLOCK 0x10 /* Caller cannot block. */
> #define ALLOC_MIN_RESERVE 0x20 /* __GFP_HIGH set. Allow access to 50%
> - * of the min watermark.
> + * of the min watermark, or 62.5% if
> + * the caller cannot block either
> + * (ALLOC_NON_BLOCK).
> */
> #define ALLOC_CPUSET 0x40 /* check for correct cpuset */
> #define ALLOC_CMA 0x80 /* allow allocations from CMA areas */
> @@ -58,7 +57,7 @@
> #define ALLOC_NO_CODETAG 0x1000
>
> /* Flags that allow allocations below the min watermark. */
> -#define ALLOC_RESERVES (ALLOC_NON_BLOCK|ALLOC_MIN_RESERVE|ALLOC_HIGHATOMIC|ALLOC_OOM)
> +#define ALLOC_RESERVES (ALLOC_MIN_RESERVE|ALLOC_HIGHATOMIC|ALLOC_OOM)
>
> /* Flags that mean GFP_ATOMIC */
> #define ALLOC_MASK_ATOMIC (ALLOC_NON_BLOCK|ALLOC_MIN_RESERVE)