Re: [PATCH 2/2] xfs: consistent low ag space behavior for sparse inode chunk allocs

Dave Chinner <[email protected]> Tue, 4 Aug 2026 08:01:30 +1000
Newsgroups org.kernel.vger.linux-xfs
Message-ID <anEPukpCKlxRxACR@dread>
On Mon, Aug 03, 2026 at 02:05:42PM -0400, Brian Foster wrote:
> On Sat, Aug 01, 2026 at 10:11:43AM +1000, Dave Chinner wrote:
> > 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.

[snip]

> > 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).

Yeah, sounds pretty normal LLM analysis -> fix progression to me.

IME, LLMs often get close to the right solution, but they lack the
subject matter expertise and/or the deep abstract thinking needed to
understand that the problem is in the underlying behavioural
constraints rather than the context triggering the issue.

Hence they output hacks to address the symptom the problematic
context displays rather than a proper fix for the underlying
issue...

> > 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.

*nod* Seems likely to me.

Also, data extent allocations often have slop in them for alignment
on top of the minleft value, so I suspect that AGFL reservation
growth could be hidden by the alignment slop that was reserved but
not used.

> > 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.

I've thought on it a bit more, too, and I now realise the original
code is correct. 

I failed to take into account that we can't split the two blocks the
old root was split into again - they will be at 50% capacity after
the first split, so a split up to one of their child nodes will only
add one more ptr to them. They can't fill and split again in the
current transaction chain.

Hence the second split can only occur up to (current height - 1),
and so the existing calculation is correct.

[snip]

> 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.

Right, that's my understanding of the issue - that min_free is an up
front worst case reservation to prevent downstream ENOSPC within the
AG during the transactional modification that is about to take
place.

i.e. it's taken over 30 years for us to realise that the AGFL
reservation could change in the middle of a multi-allocation
transaction and that the minfree calc has never taken that into
account. i.e. this is yet another zero-day bug in the AGFL fixup
code...

Cheers,

Dave.
-- 
Dave Chinner
[email protected]