All of lore.kernel.org
 help / color / mirror / Atom feed
From: Brian Foster <bfoster@redhat.com>
To: Dave Chinner <dgc@kernel.org>
Cc: linux-xfs@vger.kernel.org, Matt Fleming <matt@readmodwrite.com>
Subject: Re: [PATCH 2/2] xfs: consistent low ag space behavior for sparse inode chunk allocs
Date: Mon, 3 Aug 2026 14:05:42 -0400	[thread overview]
Message-ID: <anDYdgP6y2537cY_@bfoster> (raw)
In-Reply-To: <am05vzAntuhAKXgr@dread>

On Sat, Aug 01, 2026 at 10:11:43AM +1000, Dave Chinner wrote:
> Hi Brian,
> 
> On Fri, Jul 31, 2026 at 12:33:37PM -0400, Brian Foster wrote:
> > Matt Fleming reports a filesystem shutdown due to inobt block
> > allocation failure during sparse chunk allocation. Inode creation
> > can involve multiple allocations in a transaction via the initial
> > chunk allocation and inode btree growth via the subsequent inobt
> > record insertion. Technically this should be safe as the chunk
> > allocation sets the allocation minleft parameter to the max depth of
> > the inode btree, which means the allocation selects an AG only if
> > there is enough free space for record insertion after the
> > allocation. The record insertion naturally occurs in the same AG as
> > the allocation and the associated AGF is locked and held by the
> > current transaction.
> > 
> > The situation that reproduces this failure is a bit of a corner
> > case. The allocation and inode btrees are all completely full and
> > require a split on the next insertion. The AG has just enough
> > available space to satisfy inode creation through sparse allocation
> > (i.e. 7 blocks in this example). The block allocation occurs within
> > an existing free space record, splitting the free space record into
> > two and triggering the aforementioned allocbt splits.
> > 
> > The sparse chunk allocation consumes 4 blocks for the chunk, and 4
> > blocks from the AGFL for the two allocbt splits. This leaves the
> > AGFL with 4 remaining blocks and 3 available blocks in the AG. The
> > record insertion attempts block allocation for the inobt split, but
> > even though space is available, the geometry change has changed the
> > minimum allocation requirements enforced by
> > xfs_alloc_fix_freelist(). The min freelist value jumps from 8 to 12
> > due to the alloc btree level increases, so the available space
> > calculation goes from something like this before the allocation:
> 
> Ok, in my own words, the problem is that the minleft takes into
> account the space needed by the inobt/finobt allocations, but the
> allocator doesn't take into account that the AGFL requirement may
> increase for the second allocation in the transaction because the
> first allocation split the free space trees?
> 
> Is that an accurate summary of the issue?
> 

Yup.

> If that is the case, then I think the root cause of the issue is
> that amount we are reserving for the AGFL on the first transaction
> is insufficient. ENOSPC needs to be detected before we modify
> anything, so it has to be done at the first allocation. However, it
> appears to be based on the current level of the bno/cnt btrees, not
> the potential for the level to increase and the AGFL require a
> larger block reservation than we've accounted for with minleft.
> 
> Therefore, we need to account for the AGFL demand increase in the
> first reservation that might occur for the second (and subsequent
> allocations.
> 
> i.e. if minleft != 0, then the agfl btree block reservations need to
> be done for (current level + 1) to take into account space for the
> trees to split during the dependent allocation chain that minleft !=
> 0 implies is about to occur.
> 

Ok, this more aligns with what the LLM had suggested, at least in terms
of fixing the problem at the first AGFL check via the minfree
calculation. IIRC it wanted to bump minleft internally and that came out
rather uglier than bumping min_free (as below).

> This isn't an issue just for inobt/finobt blocks on inode chunk
> alloc, it's also an issue for anything that sets minleft for a
> dependent, multi-allocation operation (e.g. data extent allocation +
> BMBT block allocation). 
> 

Yeah, I noticed we had a handful of other cases that use minleft like
this. I wonder if the main reason we don't hit this from the bmapi path
is that it's usually open to multiple AGs for bmbt allocs and not fixed
to a single AG like inode chunk and corresponding inobt block allocs
obviously are.

> So from this perspective, I think the fix needs to be made to the
> calculation in xfs_alloc_min_freelist() to take into account minleft
> needing a larger AGFL reservation if any of the allocations in the
> chain splits.
> 
> Hmmmm. I'm not sure the calculation in xfs_alloc_min_freelist() is
> correct, either:
> 
>         /*
>          * For a btree shorter than the maximum height, the worst case is that
>          * every level gets split and a new level is added, then while inserting
>          * another entry to refill the AGFL, every level under the old root gets
>          * split again. This is:
>          *
>          *   (full height split reservation) + (AGFL refill split height)
>          * = (current height + 1) + (current height - 1)
>          * = (new height) + (new height - 2)
>          * = 2 * new height - 2
> 
> i.e. I think the AGFL refill split height is wrong.
> 
> Look at it this way: we do the AGFL refill -first-, so if that
> splits, we consume (current height + 1) blocks, and then new_height
> = (current height + 1). Then if the actual data allocation does a
> full split again (i.e. all except for the root block), that needs
> (new_height -1) blocks. So that becomes:
> 
> 	new_height = current height + 1;
> 	blocks = new_height + (new_height - 1)
> 	       = 2 * new_height - 1.
> 
> IOWs, I think there's an existing off-by one in the AGFL btree block
> reservation calculation that contributes to this problem as well.
> 

Hmm.. I need to stare at this a little more, but I think that makes
sense.

> Put these two things together:
> 
> 	/*
> 	 * if minleft is set, we are going to do multiple * allocations in this
> 	 * transaction (e.g. inode chunk followed by inobt). Hence
> 	 * we need to make sure we have enough space for fixing up
> 	 * the freelists after the split as the btree level
> 	 * increases the AGFL reservation size mid-transaction. If
> 	 * we are close to ENOSPC, this AGFL reservation increase may
> 	 * trigger ENOSPC from the subsequent allocation attempt,
> 	 * and then we shutdown the filesystem. Hence if minleft is
> 	 * set, reserve enough AGFL space for a potential split
> 	 * during the allocation chain in this transaction up front.
> 	 */
> 	split_space = 1;
> 	if (minleft)
> 		split_space = 2;
> 
>         /* space needed by-bno freespace btree */
>         min_free = min(bno_level + split_space, mp->m_alloc_maxlevels) * 2 - 1;
>         /* space needed by-size freespace btree */
>         min_free += min(cnt_level + split_space, mp->m_alloc_maxlevels) * 2 - 1;
>         /* space needed reverse mapping used space btree */
>         if (xfs_has_rmapbt(mp))
>                 min_free += min(rmap_level + split_space, mp->m_rmap_maxlevels) * 2 - 1;
>         return min_free;
> 
> This is all off the top of my head, I haven't verified it so it'll
> need checking. However, I think handling this situation generically
> in the allocator is a better solution than trying to work around it
> with caller-based minleft heuristics.
> 

I think this is reasonable. The thing that stands out a little bit is
that technically we're reserving for the worst case min_free allocation
requirement of the second alloc, not necessarily the actual number of
blocks needed, right? That seems fine if so. I just want to make sure
we're clear and we document the updated calculation appropriately (i.e.
noting how minleft > 0 is a somewhat special case).

The off-by-one thing aside, IIUC the current min_free calculation
correctly accounts the actual number of blocks we'd consume in the worst
case for both allocations, it just doesn't account for how the min_free
requirements change upon reentry into the allocator after a minleft > 0
allocation.

I'll throw some testing at this. Thanks for the feedback.

Brian

> Cheers,
> 
> Dave.
> 
> -- 
> Dave Chinner
> dgc@kernel.org
> 


  reply	other threads:[~2026-08-03 18:05 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-31 16:33 [PATCH 0/2] xfs: fix a couple sparse chunk alloc problems Brian Foster
2026-07-31 16:33 ` [PATCH 1/2] xfs: set minleft correctly for sparse chunk errortag allocation Brian Foster
2026-07-31 18:02   ` [External] : " Mark Tinguely
2026-07-31 18:40     ` Brian Foster
2026-07-31 16:33 ` [PATCH 2/2] xfs: consistent low ag space behavior for sparse inode chunk allocs Brian Foster
2026-07-31 17:54   ` [External] : " Mark Tinguely
2026-08-01  0:11   ` Dave Chinner
2026-08-03 18:05     ` Brian Foster [this message]
2026-08-03 22:01       ` Dave Chinner

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=anDYdgP6y2537cY_@bfoster \
    --to=bfoster@redhat.com \
    --cc=dgc@kernel.org \
    --cc=linux-xfs@vger.kernel.org \
    --cc=matt@readmodwrite.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.