All of lore.kernel.org
 help / color / mirror / Atom feed
From: Dave Chinner <dgc@kernel.org>
To: Brian Foster <bfoster@redhat.com>
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: Sat, 1 Aug 2026 10:11:43 +1000	[thread overview]
Message-ID: <am05vzAntuhAKXgr@dread> (raw)
In-Reply-To: <20260731163337.152522-3-bfoster@redhat.com>

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?

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.

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

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.

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.

Cheers,

Dave.

-- 
Dave Chinner
dgc@kernel.org

      parent reply	other threads:[~2026-08-01  0:11 UTC|newest]

Thread overview: 7+ 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 [this message]

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=am05vzAntuhAKXgr@dread \
    --to=dgc@kernel.org \
    --cc=bfoster@redhat.com \
    --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.