Re: [PATCH v2 2/4] mm: hugetlb: Fix out_put_pages subpool reserve calculation
From: Joshua Hahn
Date: Fri Sep 11 2026 - 10:41:24 EST
On Wed, 09 Sep 2026 14:49:27 -0700 Ackerley Tng via B4 Relay <devnull+ackerleytng.google.com@xxxxxxxxxx> wrote:
> From: Ackerley Tng <ackerleytng@xxxxxxxxxx>
>
> When reserving pages for a mapping fails during global accounting, the
> error path rolls back the adjustments made to the subpool.
>
> Currently, this rollback was performed in two separate steps:
>
> 1. Returning only the portion of reservations originally satisfied from the
> subpool
> 2. Separately adjusting the subpool used pages counter for the portion that
> was requested from the global pool.
>
> In (1.), because the used pages counter had not yet been decremented for
> the global portion, the subpool observed an inflated used pages count. If
> the mount was configured with both a minimum size and a maximum size, this
> inflated count prevented the subpool from recognizing that usage fell below
> the minimum size guarantee.
>
> As a result, the subpool failed to restore its reserved pages counter and
> instead returned that a global reservation should be dropped. The
> mount-time reservation is permanently destroyed, leaving global reservation
> counts depleted and causing an underflow when the filesystem is eventually
> unmounted.
Yeah, this sounds pretty bad.
> Additionally, if concurrent threads modified subpool usage during the
> reservation attempt, calculating the rollback amount using stale local
> variables could cause global reservation counts to diverge.
>
> Now that used pages are always tracked within the subpool, return the
> entire requested page count to the subpool in a single call. Global
> reservations are then adjusted using the difference between the
> reservations originally requested and those returned, fixing the issues
> described above.
>
> Fixes: 1d3f9bb4c8af ("mm/hugetlb: restore failed global reservations to subpool")
> Signed-off-by: Ackerley Tng <ackerleytng@xxxxxxxxxx>
> Cc: stable@xxxxxxxxxxxxxxx
Thanks! LGTM,
Reviewed-by: Joshua Hahn <joshua.hahnjy@xxxxxxxxx>
With one nit below:
> ---
> mm/hugetlb.c | 49 +++++++++++++++++++++++--------------------------
> 1 file changed, 23 insertions(+), 26 deletions(-)
>
> diff --git a/mm/hugetlb.c b/mm/hugetlb.c
> index e72e22f887478..9eb9f3442574c 100644
> --- a/mm/hugetlb.c
> +++ b/mm/hugetlb.c
> @@ -6676,12 +6676,14 @@ long hugetlb_reserve_pages(struct inode *inode,
> struct vm_area_struct *vma,
> vma_flags_t vma_flags)
> {
> - long chg = -1, add = -1, spool_resv, gbl_resv;
> + long chg = -1, add = -1;
> struct hstate *h = hstate_inode(inode);
> struct hugepage_subpool *spool = subpool_inode(inode);
> struct resv_map *resv_map;
> struct hugetlb_cgroup *h_cg = NULL;
> - long gbl_reserve, regions_needed = 0;
> + long regions_needed = 0;
> + long gbl_resv_get;
> + long gbl_resv_put;
> int err;
>
> /* This should never happen */
> @@ -6756,9 +6758,9 @@ long hugetlb_reserve_pages(struct inode *inode,
> * the subpool has a minimum size, there may be some global
> * reservations already in place (gbl_reserve).
> */
> - gbl_reserve = hugepage_subpool_get_pages(spool, chg);
> - if (gbl_reserve < 0) {
> - err = gbl_reserve;
> + gbl_resv_get = hugepage_subpool_get_pages(spool, chg);
> + if (gbl_resv_get < 0) {
> + err = gbl_resv_get;
> goto out_uncharge_cgroup;
> }
>
> @@ -6766,7 +6768,7 @@ long hugetlb_reserve_pages(struct inode *inode,
> * Check enough hugepages are available for the reservation.
> * Hand the pages back to the subpool if there are not
> */
> - err = hugetlb_acct_memory(h, gbl_reserve);
> + err = hugetlb_acct_memory(h, gbl_resv_get);
> if (err < 0)
> goto out_put_pages;
>
> @@ -6785,7 +6787,7 @@ long hugetlb_reserve_pages(struct inode *inode,
> add = region_add(resv_map, from, to, regions_needed, h, h_cg);
>
> if (unlikely(add < 0)) {
> - hugetlb_acct_memory(h, -gbl_reserve);
> + hugetlb_acct_memory(h, -gbl_resv_get);
> err = add;
> goto out_put_pages;
> } else if (unlikely(chg > add)) {
> @@ -6821,26 +6823,21 @@ long hugetlb_reserve_pages(struct inode *inode,
> }
> return chg;
>
> -out_put_pages:
> - spool_resv = chg - gbl_reserve;
> - if (spool_resv) {
> - /* put sub pool's reservation back, chg - gbl_reserve */
> - gbl_resv = hugepage_subpool_put_pages(spool, spool_resv);
> - /*
> - * subpool's reserved pages can not be put back due to race,
> - * return to hstate.
> - */
> - hugetlb_acct_memory(h, -gbl_resv);
> - }
> - /* Restore used_hpages for pages that failed global reservation */
> - if (gbl_reserve && spool) {
> - unsigned long flags;
> + out_put_pages:
I'm not sure if this section warrants these two comment blocks anymore.
The logic is quite straightforward now that we don't have to worry about
managing the global / subpool reservations separately and rather just
simply do the subtraction to account the memory.
> + /*
> + * Return all that was requested from the subpool, let subpool
> + * tell us the new number of reservations that need to be
> + * returned to the global pool.
> + */
> + gbl_resv_put = hugepage_subpool_put_pages(spool, chg);
> + /*
> + * There may be a difference between the number of
> + * reservations to consume and the number to restore now if
> + * there are multiple threads interacting with the subpool -
> + * restore the difference.
> + */
> + hugetlb_acct_memory(h, gbl_resv_get - gbl_resv_put);
>
> - spin_lock_irqsave(&spool->lock, flags);
> - if (spool->max_hpages != -1)
> - spool->used_hpages -= gbl_reserve;
> - unlock_or_release_subpool(spool, flags);
> - }
> out_uncharge_cgroup:
> hugetlb_cgroup_uncharge_cgroup_rsvd(hstate_index(h),
> chg * pages_per_huge_page(h), h_cg);
>
> --
> 2.55.0.1007.g17ff1f9808-goog