* [PATCH v2 0/3] xfs: fix a couple sparse chunk alloc problems
@ 2026-08-14 13:22 Brian Foster
2026-08-14 13:22 ` [PATCH v2 1/3] xfs: set minleft correctly for sparse chunk errortag allocation Brian Foster
` (2 more replies)
0 siblings, 3 replies; 9+ messages in thread
From: Brian Foster @ 2026-08-14 13:22 UTC (permalink / raw)
To: linux-xfs; +Cc: Matt Fleming
Hi all,
Here's v2 of the series to fix the sparse chunk alloc shutdown. The
original report is available here[1]. After some discussion on v1, this
takes a slightly different approach to treat this as an allocator flaw.
The solution is essentially to add the additional min_free block
requirement to minleft for allocs where minleft != 0. See the commit log
for patch 3 for further detail.
Patch 1 is the same DEBUG mode bug fix from v1. Patch 2 refactors the
agfl min free helper for use in patch 3. Patch 3 makes the actual logic
fix.
This survives some fstests runs without regression. For some reason the
sparse inode reproducer is failing to work properly on the current
baseline (can't create the requisite fs state) so I can't reconfirm
that, but I don't expect anything would have changed there.
Thoughts, reviews, flames appreciated.
Brian
v2:
- Reworked fix logic into allocator instead of sparse inode alloc
specific.
- Dropped Fixes: tag since this is no longer directly correlated to
sparse inodes.
v1: https://lore.kernel.org/linux-xfs/20260731163337.152522-1-bfoster@redhat.com/
[1] https://lore.kernel.org/linux-xfs/20260717130429.1838767-1-matt@readmodwrite.com/
Brian Foster (3):
xfs: set minleft correctly for sparse chunk errortag allocation
xfs: support additional levels in the agfl minimum calculation
xfs: incorporate increased AGFL min requirement for minleft allocs
fs/xfs/libxfs/xfs_alloc.c | 60 ++++++++++++++++++++++++++++++++------
fs/xfs/libxfs/xfs_alloc.h | 2 ++
fs/xfs/libxfs/xfs_bmap.c | 2 +-
fs/xfs/libxfs/xfs_ialloc.c | 10 +++----
4 files changed, 58 insertions(+), 16 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v2 1/3] xfs: set minleft correctly for sparse chunk errortag allocation 2026-08-14 13:22 [PATCH v2 0/3] xfs: fix a couple sparse chunk alloc problems Brian Foster @ 2026-08-14 13:22 ` Brian Foster 2026-08-14 13:22 ` [PATCH v2 2/3] xfs: support additional levels in the agfl minimum calculation Brian Foster 2026-08-14 13:22 ` [PATCH v2 3/3] xfs: incorporate increased AGFL min requirement for minleft allocs Brian Foster 2 siblings, 0 replies; 9+ messages in thread From: Brian Foster @ 2026-08-14 13:22 UTC (permalink / raw) To: linux-xfs; +Cc: Matt Fleming The errortag instrumentation for forced sparse chunk allocation jumps straight to the allocation path without setting args.minleft. minleft is unconditionally set to ->inobt_maxlevels for the normal allocation path. Lift the assignment to the initial args setup so it covers all possible paths. Assisted-by: LLM Fixes: 1cdadee11f8d ("xfs: randomly do sparse inode allocations in DEBUG mode") Signed-off-by: Brian Foster <bfoster@redhat.com> Reviewed-by: Mark Tinguely <mark.tinguely@oracle.com> --- fs/xfs/libxfs/xfs_ialloc.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/fs/xfs/libxfs/xfs_ialloc.c b/fs/xfs/libxfs/xfs_ialloc.c index ffcdd1f691fd..633b2d6e42c5 100644 --- a/fs/xfs/libxfs/xfs_ialloc.c +++ b/fs/xfs/libxfs/xfs_ialloc.c @@ -733,6 +733,10 @@ xfs_ialloc_ag_alloc( igeo->maxicount) return -ENOSPC; args.minlen = args.maxlen = igeo->ialloc_blks; + + /* Allow space for the inode btree to split. */ + args.minleft = igeo->inobt_maxlevels; + /* * First try to allocate inodes contiguous with the last-allocated * chunk of inodes. If the filesystem is striped, this will fill @@ -764,8 +768,6 @@ xfs_ialloc_ag_alloc( args.alignment = 1; args.minalignslop = igeo->cluster_align - 1; - /* Allow space for the inode btree to split. */ - args.minleft = igeo->inobt_maxlevels; error = xfs_alloc_vextent_exact_bno(&args, xfs_agbno_to_fsb(pag, args.agbno)); if (error) @@ -804,10 +806,6 @@ xfs_ialloc_ag_alloc( * Allocate a fixed-size extent of inodes. */ args.prod = 1; - /* - * Allow space for the inode btree to split. - */ - args.minleft = igeo->inobt_maxlevels; error = xfs_alloc_vextent_near_bno(&args, xfs_agbno_to_fsb(pag, be32_to_cpu(agi->agi_root))); -- 2.55.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2 2/3] xfs: support additional levels in the agfl minimum calculation 2026-08-14 13:22 [PATCH v2 0/3] xfs: fix a couple sparse chunk alloc problems Brian Foster 2026-08-14 13:22 ` [PATCH v2 1/3] xfs: set minleft correctly for sparse chunk errortag allocation Brian Foster @ 2026-08-14 13:22 ` Brian Foster 2026-08-14 13:22 ` [PATCH v2 3/3] xfs: incorporate increased AGFL min requirement for minleft allocs Brian Foster 2 siblings, 0 replies; 9+ messages in thread From: Brian Foster @ 2026-08-14 13:22 UTC (permalink / raw) To: linux-xfs; +Cc: Matt Fleming xfs_alloc_min_freelist() calculates the worst case AGFL block requirement for a full split plus partial refill for each alloc btree. An upcoming patch needs to calculate the requirement for multiple level increases, so add an optional extra levels parameter and factor out a wrapper function for the common case of a single split. No functional changes. Assisted-by: LLM Signed-off-by: Brian Foster <bfoster@redhat.com> --- fs/xfs/libxfs/xfs_alloc.c | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c index d99602bcc16f..dbb85fb6314b 100644 --- a/fs/xfs/libxfs/xfs_alloc.c +++ b/fs/xfs/libxfs/xfs_alloc.c @@ -2439,17 +2439,22 @@ xfs_alloc_longest_free_extent( /* * Compute the minimum length of the AGFL in the given AG. If @pag is NULL, - * return the largest possible minimum length. + * return the largest possible minimum length. The base calculation accounts + * for a single full split per btree. @extra_levels adds additional split + * levels to compute the prospective AGFL requirement increase for + * multi-allocation transactions. */ -unsigned int -xfs_alloc_min_freelist( +static unsigned int +__xfs_alloc_min_freelist( struct xfs_mount *mp, - struct xfs_perag *pag) + struct xfs_perag *pag, + unsigned int extra_levels) { /* AG btrees have at least 1 level. */ const unsigned int bno_level = pag ? pag->pagf_bno_level : 1; const unsigned int cnt_level = pag ? pag->pagf_cnt_level : 1; const unsigned int rmap_level = pag ? pag->pagf_rmap_level : 1; + const unsigned int levels = 1 + extra_levels; unsigned int min_free; ASSERT(mp->m_alloc_maxlevels > 0); @@ -2476,15 +2481,25 @@ xfs_alloc_min_freelist( */ /* space needed by-bno freespace btree */ - min_free = min(bno_level + 1, mp->m_alloc_maxlevels) * 2 - 2; + min_free = min(bno_level + levels, mp->m_alloc_maxlevels) * 2 - 2; /* space needed by-size freespace btree */ - min_free += min(cnt_level + 1, mp->m_alloc_maxlevels) * 2 - 2; + min_free += min(cnt_level + levels, mp->m_alloc_maxlevels) * 2 - 2; /* space needed reverse mapping used space btree */ - if (xfs_has_rmapbt(mp)) - min_free += min(rmap_level + 1, mp->m_rmap_maxlevels) * 2 - 2; + if (xfs_has_rmapbt(mp)) { + min_free += min(rmap_level + levels, + mp->m_rmap_maxlevels) * 2 - 2; + } return min_free; } +unsigned int +xfs_alloc_min_freelist( + struct xfs_mount *mp, + struct xfs_perag *pag) +{ + return __xfs_alloc_min_freelist(mp, pag, 0); +} + /* * Check if the operation we are fixing up the freelist for should go ahead or * not. If we are freeing blocks, we always allow it, otherwise the allocation -- 2.55.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2 3/3] xfs: incorporate increased AGFL min requirement for minleft allocs 2026-08-14 13:22 [PATCH v2 0/3] xfs: fix a couple sparse chunk alloc problems Brian Foster 2026-08-14 13:22 ` [PATCH v2 1/3] xfs: set minleft correctly for sparse chunk errortag allocation Brian Foster 2026-08-14 13:22 ` [PATCH v2 2/3] xfs: support additional levels in the agfl minimum calculation Brian Foster @ 2026-08-14 13:22 ` Brian Foster 2026-08-14 13:57 ` Brian Foster 2 siblings, 1 reply; 9+ messages in thread From: Brian Foster @ 2026-08-14 13:22 UTC (permalink / raw) To: linux-xfs; +Cc: Matt Fleming 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: the initial chunk allocation and inode btree blocks via inobt record insertion. This is expected to be safe by using the minleft parameter on the chunk allocation to guarantee the selected AG has blocks available for a followup inobt insertion. The sequence that leads to this failure is that the alloc and inode btrees are all full (require a full split on next insertion) and the AG has just enough free space to satisfy a sparse chunk allocation with minleft set (i.e. 7 blocks in this example). The chunk allocation splits a free extent, triggers full allocbt splits, and consumes 4 free blocks for the chunk and 4 AGFL blocks for the btrees. Next, the inobt record insertion triggers an inobt split. The AG has enough free blocks, but the allocbt splits caused by the chunk allocation have increased the min AGFL requirement for the AG due to btree level increases. The AGFL requirement as calculated by xfs_alloc_fix_freelist() is: free + AGFL - res - minfree - minleft = avail This evaluates to the following on initial chunk allocation: 2514 + 8 - 2505 - 8 - 2 = 7 ... and then after the chunk allocation but before the inobt block allocation: 2510 + 4 - 2505 - 12 - 0 = -3 This causes the inobt alloc to fail despite minleft being set in the first allocation. The error path cancels the dirty transaction and shuts down the fs. The problem here is that while minleft ensures free blocks are available for the inobt insert, it is not sufficient to cover the increase of the AGFL min free requirement. To address this, create a variant of the AGFL min free calculation for minleft allocations that incorporates an additional allocbt level increase. We do not add the additional blocks directly to min_free because this would lead to spurious AGFL block allocations and frees in the common case (i.e. no btree splits). Instead, add the surplus block requirement to the minleft value used to select the AG. This ensures the AG has enough blocks for the caller's minleft value plus the worst case increase in the AGFL. In the example above, the initial calculation now evaluates to 3 blocks available instead of 7 and the sparse inode allocation fails gracefully with -ENOSPC. Reported-by: Matt Fleming <matt@readmodwrite.com> Assisted-by: LLM Signed-off-by: Brian Foster <bfoster@redhat.com> --- fs/xfs/libxfs/xfs_alloc.c | 29 ++++++++++++++++++++++++++++- fs/xfs/libxfs/xfs_alloc.h | 2 ++ fs/xfs/libxfs/xfs_bmap.c | 2 +- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c index dbb85fb6314b..74c5b587c87b 100644 --- a/fs/xfs/libxfs/xfs_alloc.c +++ b/fs/xfs/libxfs/xfs_alloc.c @@ -2500,6 +2500,20 @@ xfs_alloc_min_freelist( return __xfs_alloc_min_freelist(mp, pag, 0); } +/* + * Return the minimum freelist requirement considering a potential allocbt split + * from the current allocation. Use this when computing longest free extent for + * allocations with minleft set to ensure that the available extent length + * accounts for the subsequent allocation's increased AGFL requirement. + */ +unsigned int +xfs_alloc_min_freelist_minleft( + struct xfs_mount *mp, + struct xfs_perag *pag) +{ + return __xfs_alloc_min_freelist(mp, pag, 1); +} + /* * Check if the operation we are fixing up the freelist for should go ahead or * not. If we are freeing blocks, we always allow it, otherwise the allocation @@ -2517,6 +2531,7 @@ xfs_alloc_space_available( xfs_extlen_t reservation; /* blocks that are still reserved */ int available; xfs_extlen_t agflcount; + xfs_extlen_t minleft; if (flags & XFS_ALLOC_FLAG_FREEING) return true; @@ -2533,10 +2548,22 @@ xfs_alloc_space_available( * Do we have enough free space remaining for the allocation? Don't * account extra agfl blocks because we are about to defer free them, * making them unavailable until the current transaction commits. + * + * If minleft is set, this allocation might cause an allocbt split that + * increases the AGFL minimum for the next allocation in the + * transaction. Reserve that space from the available block count + * (without prematurely growing the AGFL) to prevent the subsequent + * allocation from failing due to an increased min_free requirement. */ + minleft = args->minleft; + if (minleft) { + minleft += xfs_alloc_min_freelist_minleft(args->mp, pag) - + min_free; + } + agflcount = min_t(xfs_extlen_t, pag->pagf_flcount, min_free); available = (int)(pag->pagf_freeblks + agflcount - - reservation - min_free - args->minleft); + reservation - min_free - minleft); if (available < (int)max(args->total, alloc_len)) return false; diff --git a/fs/xfs/libxfs/xfs_alloc.h b/fs/xfs/libxfs/xfs_alloc.h index 50ef79a1ed41..026b61a63994 100644 --- a/fs/xfs/libxfs/xfs_alloc.h +++ b/fs/xfs/libxfs/xfs_alloc.h @@ -73,6 +73,8 @@ xfs_extlen_t xfs_alloc_longest_free_extent(struct xfs_perag *pag, xfs_extlen_t need, xfs_extlen_t reserved); unsigned int xfs_alloc_min_freelist(struct xfs_mount *mp, struct xfs_perag *pag); +unsigned int xfs_alloc_min_freelist_minleft(struct xfs_mount *mp, + struct xfs_perag *pag); int xfs_alloc_get_freelist(struct xfs_perag *pag, struct xfs_trans *tp, struct xfs_buf *agfbp, xfs_agblock_t *bnop, int btreeblk); int xfs_alloc_put_freelist(struct xfs_perag *pag, struct xfs_trans *tp, diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c index d64defeda645..f396df864cf4 100644 --- a/fs/xfs/libxfs/xfs_bmap.c +++ b/fs/xfs/libxfs/xfs_bmap.c @@ -3160,7 +3160,7 @@ xfs_bmap_longest_free_extent( } longest = xfs_alloc_longest_free_extent(pag, - xfs_alloc_min_freelist(pag_mount(pag), pag), + xfs_alloc_min_freelist_minleft(pag_mount(pag), pag), xfs_ag_resv_needed(pag, XFS_AG_RESV_NONE)); if (*blen < longest) *blen = longest; -- 2.55.0 ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v2 3/3] xfs: incorporate increased AGFL min requirement for minleft allocs 2026-08-14 13:22 ` [PATCH v2 3/3] xfs: incorporate increased AGFL min requirement for minleft allocs Brian Foster @ 2026-08-14 13:57 ` Brian Foster 2026-08-14 14:15 ` [External] : " Mark Tinguely 0 siblings, 1 reply; 9+ messages in thread From: Brian Foster @ 2026-08-14 13:57 UTC (permalink / raw) To: linux-xfs; +Cc: Matt Fleming On Fri, Aug 14, 2026 at 09:22:39AM -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: the initial chunk > allocation and inode btree blocks via inobt record insertion. This > is expected to be safe by using the minleft parameter on the chunk > allocation to guarantee the selected AG has blocks available for > a followup inobt insertion. > > The sequence that leads to this failure is that the alloc and inode > btrees are all full (require a full split on next insertion) and the > AG has just enough free space to satisfy a sparse chunk allocation > with minleft set (i.e. 7 blocks in this example). The chunk > allocation splits a free extent, triggers full allocbt splits, and > consumes 4 free blocks for the chunk and 4 AGFL blocks for the > btrees. > > Next, the inobt record insertion triggers an inobt split. The AG has > enough free blocks, but the allocbt splits caused by the chunk > allocation have increased the min AGFL requirement for the AG due to > btree level increases. The AGFL requirement as calculated by > xfs_alloc_fix_freelist() is: > > free + AGFL - res - minfree - minleft = avail > > This evaluates to the following on initial chunk allocation: > > 2514 + 8 - 2505 - 8 - 2 = 7 > > ... and then after the chunk allocation but before the inobt block > allocation: > > 2510 + 4 - 2505 - 12 - 0 = -3 > > This causes the inobt alloc to fail despite minleft being set in the > first allocation. The error path cancels the dirty transaction and > shuts down the fs. > > The problem here is that while minleft ensures free blocks are > available for the inobt insert, it is not sufficient to cover the > increase of the AGFL min free requirement. To address this, create a > variant of the AGFL min free calculation for minleft allocations > that incorporates an additional allocbt level increase. > > We do not add the additional blocks directly to min_free because > this would lead to spurious AGFL block allocations and frees in the > common case (i.e. no btree splits). Instead, add the surplus block > requirement to the minleft value used to select the AG. This ensures > the AG has enough blocks for the caller's minleft value plus the > worst case increase in the AGFL. In the example above, the initial > calculation now evaluates to 3 blocks available instead of 7 and the > sparse inode allocation fails gracefully with -ENOSPC. > > Reported-by: Matt Fleming <matt@readmodwrite.com> > Assisted-by: LLM > Signed-off-by: Brian Foster <bfoster@redhat.com> > --- > fs/xfs/libxfs/xfs_alloc.c | 29 ++++++++++++++++++++++++++++- > fs/xfs/libxfs/xfs_alloc.h | 2 ++ > fs/xfs/libxfs/xfs_bmap.c | 2 +- > 3 files changed, 31 insertions(+), 2 deletions(-) > > diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c > index dbb85fb6314b..74c5b587c87b 100644 > --- a/fs/xfs/libxfs/xfs_alloc.c > +++ b/fs/xfs/libxfs/xfs_alloc.c > @@ -2500,6 +2500,20 @@ xfs_alloc_min_freelist( > return __xfs_alloc_min_freelist(mp, pag, 0); > } > > +/* > + * Return the minimum freelist requirement considering a potential allocbt split > + * from the current allocation. Use this when computing longest free extent for > + * allocations with minleft set to ensure that the available extent length > + * accounts for the subsequent allocation's increased AGFL requirement. > + */ > +unsigned int > +xfs_alloc_min_freelist_minleft( > + struct xfs_mount *mp, > + struct xfs_perag *pag) > +{ > + return __xfs_alloc_min_freelist(mp, pag, 1); > +} > + > /* > * Check if the operation we are fixing up the freelist for should go ahead or > * not. If we are freeing blocks, we always allow it, otherwise the allocation > @@ -2517,6 +2531,7 @@ xfs_alloc_space_available( > xfs_extlen_t reservation; /* blocks that are still reserved */ > int available; > xfs_extlen_t agflcount; > + xfs_extlen_t minleft; > > if (flags & XFS_ALLOC_FLAG_FREEING) > return true; > @@ -2533,10 +2548,22 @@ xfs_alloc_space_available( > * Do we have enough free space remaining for the allocation? Don't > * account extra agfl blocks because we are about to defer free them, > * making them unavailable until the current transaction commits. > + * > + * If minleft is set, this allocation might cause an allocbt split that > + * increases the AGFL minimum for the next allocation in the > + * transaction. Reserve that space from the available block count > + * (without prematurely growing the AGFL) to prevent the subsequent > + * allocation from failing due to an increased min_free requirement. > */ > + minleft = args->minleft; > + if (minleft) { > + minleft += xfs_alloc_min_freelist_minleft(args->mp, pag) - > + min_free; > + } > + > agflcount = min_t(xfs_extlen_t, pag->pagf_flcount, min_free); > available = (int)(pag->pagf_freeblks + agflcount - > - reservation - min_free - args->minleft); > + reservation - min_free - minleft); > if (available < (int)max(args->total, alloc_len)) > return false; > > diff --git a/fs/xfs/libxfs/xfs_alloc.h b/fs/xfs/libxfs/xfs_alloc.h > index 50ef79a1ed41..026b61a63994 100644 > --- a/fs/xfs/libxfs/xfs_alloc.h > +++ b/fs/xfs/libxfs/xfs_alloc.h > @@ -73,6 +73,8 @@ xfs_extlen_t xfs_alloc_longest_free_extent(struct xfs_perag *pag, > xfs_extlen_t need, xfs_extlen_t reserved); > unsigned int xfs_alloc_min_freelist(struct xfs_mount *mp, > struct xfs_perag *pag); > +unsigned int xfs_alloc_min_freelist_minleft(struct xfs_mount *mp, > + struct xfs_perag *pag); > int xfs_alloc_get_freelist(struct xfs_perag *pag, struct xfs_trans *tp, > struct xfs_buf *agfbp, xfs_agblock_t *bnop, int btreeblk); > int xfs_alloc_put_freelist(struct xfs_perag *pag, struct xfs_trans *tp, > diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c > index d64defeda645..f396df864cf4 100644 > --- a/fs/xfs/libxfs/xfs_bmap.c > +++ b/fs/xfs/libxfs/xfs_bmap.c > @@ -3160,7 +3160,7 @@ xfs_bmap_longest_free_extent( > } > I intended to add a comment here but lost track. I.e., something like: /* * Use the minleft freelist helper because minleft can be set for bmbt * allocs. If we don't factor it in here, the alloc can be sized * incorrectly and fail. */ I'll wait for any further feedback before reposting. Brian > longest = xfs_alloc_longest_free_extent(pag, > - xfs_alloc_min_freelist(pag_mount(pag), pag), > + xfs_alloc_min_freelist_minleft(pag_mount(pag), pag), > xfs_ag_resv_needed(pag, XFS_AG_RESV_NONE)); > if (*blen < longest) > *blen = longest; > -- > 2.55.0 > > ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [External] : Re: [PATCH v2 3/3] xfs: incorporate increased AGFL min requirement for minleft allocs 2026-08-14 13:57 ` Brian Foster @ 2026-08-14 14:15 ` Mark Tinguely 2026-08-14 14:47 ` Brian Foster 0 siblings, 1 reply; 9+ messages in thread From: Mark Tinguely @ 2026-08-14 14:15 UTC (permalink / raw) To: Brian Foster, linux-xfs; +Cc: Matt Fleming On 8/14/26 8:57 AM, Brian Foster wrote: > On Fri, Aug 14, 2026 at 09:22:39AM -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: the initial chunk >> allocation and inode btree blocks via inobt record insertion. This >> is expected to be safe by using the minleft parameter on the chunk >> allocation to guarantee the selected AG has blocks available for >> a followup inobt insertion. >> >> The sequence that leads to this failure is that the alloc and inode >> btrees are all full (require a full split on next insertion) and the >> AG has just enough free space to satisfy a sparse chunk allocation >> with minleft set (i.e. 7 blocks in this example). The chunk >> allocation splits a free extent, triggers full allocbt splits, and >> consumes 4 free blocks for the chunk and 4 AGFL blocks for the >> btrees. >> >> Next, the inobt record insertion triggers an inobt split. The AG has >> enough free blocks, but the allocbt splits caused by the chunk >> allocation have increased the min AGFL requirement for the AG due to >> btree level increases. The AGFL requirement as calculated by >> xfs_alloc_fix_freelist() is: >> >> free + AGFL - res - minfree - minleft = avail >> >> This evaluates to the following on initial chunk allocation: >> >> 2514 + 8 - 2505 - 8 - 2 = 7 >> >> ... and then after the chunk allocation but before the inobt block >> allocation: >> >> 2510 + 4 - 2505 - 12 - 0 = -3 >> >> This causes the inobt alloc to fail despite minleft being set in the >> first allocation. The error path cancels the dirty transaction and >> shuts down the fs. >> >> The problem here is that while minleft ensures free blocks are >> available for the inobt insert, it is not sufficient to cover the >> increase of the AGFL min free requirement. To address this, create a >> variant of the AGFL min free calculation for minleft allocations >> that incorporates an additional allocbt level increase. >> >> We do not add the additional blocks directly to min_free because >> this would lead to spurious AGFL block allocations and frees in the >> common case (i.e. no btree splits). Instead, add the surplus block >> requirement to the minleft value used to select the AG. This ensures >> the AG has enough blocks for the caller's minleft value plus the >> worst case increase in the AGFL. In the example above, the initial >> calculation now evaluates to 3 blocks available instead of 7 and the >> sparse inode allocation fails gracefully with -ENOSPC. >> >> Reported-by: Matt Fleming <matt@readmodwrite.com> >> Assisted-by: LLM >> Signed-off-by: Brian Foster <bfoster@redhat.com> >> --- >> fs/xfs/libxfs/xfs_alloc.c | 29 ++++++++++++++++++++++++++++- >> fs/xfs/libxfs/xfs_alloc.h | 2 ++ >> fs/xfs/libxfs/xfs_bmap.c | 2 +- >> 3 files changed, 31 insertions(+), 2 deletions(-) >> >> diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c >> index dbb85fb6314b..74c5b587c87b 100644 >> --- a/fs/xfs/libxfs/xfs_alloc.c >> +++ b/fs/xfs/libxfs/xfs_alloc.c >> @@ -2500,6 +2500,20 @@ xfs_alloc_min_freelist( >> return __xfs_alloc_min_freelist(mp, pag, 0); >> } >> >> +/* >> + * Return the minimum freelist requirement considering a potential allocbt split >> + * from the current allocation. Use this when computing longest free extent for >> + * allocations with minleft set to ensure that the available extent length >> + * accounts for the subsequent allocation's increased AGFL requirement. >> + */ >> +unsigned int >> +xfs_alloc_min_freelist_minleft( >> + struct xfs_mount *mp, >> + struct xfs_perag *pag) >> +{ >> + return __xfs_alloc_min_freelist(mp, pag, 1); >> +} >> + >> /* >> * Check if the operation we are fixing up the freelist for should go ahead or >> * not. If we are freeing blocks, we always allow it, otherwise the allocation >> @@ -2517,6 +2531,7 @@ xfs_alloc_space_available( >> xfs_extlen_t reservation; /* blocks that are still reserved */ >> int available; >> xfs_extlen_t agflcount; >> + xfs_extlen_t minleft; >> >> if (flags & XFS_ALLOC_FLAG_FREEING) >> return true; >> @@ -2533,10 +2548,22 @@ xfs_alloc_space_available( >> * Do we have enough free space remaining for the allocation? Don't >> * account extra agfl blocks because we are about to defer free them, >> * making them unavailable until the current transaction commits. >> + * >> + * If minleft is set, this allocation might cause an allocbt split that >> + * increases the AGFL minimum for the next allocation in the >> + * transaction. Reserve that space from the available block count >> + * (without prematurely growing the AGFL) to prevent the subsequent >> + * allocation from failing due to an increased min_free requirement. >> */ >> + minleft = args->minleft; >> + if (minleft) { >> + minleft += xfs_alloc_min_freelist_minleft(args->mp, pag) - >> + min_free; >> + } >> + >> agflcount = min_t(xfs_extlen_t, pag->pagf_flcount, min_free); >> available = (int)(pag->pagf_freeblks + agflcount - >> - reservation - min_free - args->minleft); >> + reservation - min_free - minleft); >> if (available < (int)max(args->total, alloc_len)) >> return false; >> >> diff --git a/fs/xfs/libxfs/xfs_alloc.h b/fs/xfs/libxfs/xfs_alloc.h >> index 50ef79a1ed41..026b61a63994 100644 >> --- a/fs/xfs/libxfs/xfs_alloc.h >> +++ b/fs/xfs/libxfs/xfs_alloc.h >> @@ -73,6 +73,8 @@ xfs_extlen_t xfs_alloc_longest_free_extent(struct xfs_perag *pag, >> xfs_extlen_t need, xfs_extlen_t reserved); >> unsigned int xfs_alloc_min_freelist(struct xfs_mount *mp, >> struct xfs_perag *pag); >> +unsigned int xfs_alloc_min_freelist_minleft(struct xfs_mount *mp, >> + struct xfs_perag *pag); >> int xfs_alloc_get_freelist(struct xfs_perag *pag, struct xfs_trans *tp, >> struct xfs_buf *agfbp, xfs_agblock_t *bnop, int btreeblk); >> int xfs_alloc_put_freelist(struct xfs_perag *pag, struct xfs_trans *tp, >> diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c >> index d64defeda645..f396df864cf4 100644 >> --- a/fs/xfs/libxfs/xfs_bmap.c >> +++ b/fs/xfs/libxfs/xfs_bmap.c >> @@ -3160,7 +3160,7 @@ xfs_bmap_longest_free_extent( >> } >> > > I intended to add a comment here but lost track. I.e., something like: > > /* > * Use the minleft freelist helper because minleft can be set for bmbt > * allocs. If we don't factor it in here, the alloc can be sized > * incorrectly and fail. > */ > > I'll wait for any further feedback before reposting. > > Brian > >> longest = xfs_alloc_longest_free_extent(pag, >> - xfs_alloc_min_freelist(pag_mount(pag), pag), >> + xfs_alloc_min_freelist_minleft(pag_mount(pag), pag), >> xfs_ag_resv_needed(pag, XFS_AG_RESV_NONE)); >> if (*blen < longest) >> *blen = longest; >> -- >> 2.55.0 >> >> thank you for the comment clarification. I was confused by that change. Could you CC stable. We saw a version of this problem in Linux 5.15 ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [External] : Re: [PATCH v2 3/3] xfs: incorporate increased AGFL min requirement for minleft allocs 2026-08-14 14:15 ` [External] : " Mark Tinguely @ 2026-08-14 14:47 ` Brian Foster 2026-08-14 15:18 ` Mark Tinguely 0 siblings, 1 reply; 9+ messages in thread From: Brian Foster @ 2026-08-14 14:47 UTC (permalink / raw) To: Mark Tinguely; +Cc: linux-xfs, Matt Fleming On Fri, Aug 14, 2026 at 09:15:15AM -0500, Mark Tinguely wrote: > On 8/14/26 8:57 AM, Brian Foster wrote: > > On Fri, Aug 14, 2026 at 09:22:39AM -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: the initial chunk > > > allocation and inode btree blocks via inobt record insertion. This > > > is expected to be safe by using the minleft parameter on the chunk > > > allocation to guarantee the selected AG has blocks available for > > > a followup inobt insertion. > > > > > > The sequence that leads to this failure is that the alloc and inode > > > btrees are all full (require a full split on next insertion) and the > > > AG has just enough free space to satisfy a sparse chunk allocation > > > with minleft set (i.e. 7 blocks in this example). The chunk > > > allocation splits a free extent, triggers full allocbt splits, and > > > consumes 4 free blocks for the chunk and 4 AGFL blocks for the > > > btrees. > > > > > > Next, the inobt record insertion triggers an inobt split. The AG has > > > enough free blocks, but the allocbt splits caused by the chunk > > > allocation have increased the min AGFL requirement for the AG due to > > > btree level increases. The AGFL requirement as calculated by > > > xfs_alloc_fix_freelist() is: > > > > > > free + AGFL - res - minfree - minleft = avail > > > > > > This evaluates to the following on initial chunk allocation: > > > > > > 2514 + 8 - 2505 - 8 - 2 = 7 > > > > > > ... and then after the chunk allocation but before the inobt block > > > allocation: > > > > > > 2510 + 4 - 2505 - 12 - 0 = -3 > > > > > > This causes the inobt alloc to fail despite minleft being set in the > > > first allocation. The error path cancels the dirty transaction and > > > shuts down the fs. > > > > > > The problem here is that while minleft ensures free blocks are > > > available for the inobt insert, it is not sufficient to cover the > > > increase of the AGFL min free requirement. To address this, create a > > > variant of the AGFL min free calculation for minleft allocations > > > that incorporates an additional allocbt level increase. > > > > > > We do not add the additional blocks directly to min_free because > > > this would lead to spurious AGFL block allocations and frees in the > > > common case (i.e. no btree splits). Instead, add the surplus block > > > requirement to the minleft value used to select the AG. This ensures > > > the AG has enough blocks for the caller's minleft value plus the > > > worst case increase in the AGFL. In the example above, the initial > > > calculation now evaluates to 3 blocks available instead of 7 and the > > > sparse inode allocation fails gracefully with -ENOSPC. > > > > > > Reported-by: Matt Fleming <matt@readmodwrite.com> > > > Assisted-by: LLM > > > Signed-off-by: Brian Foster <bfoster@redhat.com> > > > --- > > > fs/xfs/libxfs/xfs_alloc.c | 29 ++++++++++++++++++++++++++++- > > > fs/xfs/libxfs/xfs_alloc.h | 2 ++ > > > fs/xfs/libxfs/xfs_bmap.c | 2 +- > > > 3 files changed, 31 insertions(+), 2 deletions(-) > > > > > > diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c > > > index dbb85fb6314b..74c5b587c87b 100644 > > > --- a/fs/xfs/libxfs/xfs_alloc.c > > > +++ b/fs/xfs/libxfs/xfs_alloc.c > > > @@ -2500,6 +2500,20 @@ xfs_alloc_min_freelist( > > > return __xfs_alloc_min_freelist(mp, pag, 0); > > > } > > > +/* > > > + * Return the minimum freelist requirement considering a potential allocbt split > > > + * from the current allocation. Use this when computing longest free extent for > > > + * allocations with minleft set to ensure that the available extent length > > > + * accounts for the subsequent allocation's increased AGFL requirement. > > > + */ > > > +unsigned int > > > +xfs_alloc_min_freelist_minleft( > > > + struct xfs_mount *mp, > > > + struct xfs_perag *pag) > > > +{ > > > + return __xfs_alloc_min_freelist(mp, pag, 1); > > > +} > > > + > > > /* > > > * Check if the operation we are fixing up the freelist for should go ahead or > > > * not. If we are freeing blocks, we always allow it, otherwise the allocation > > > @@ -2517,6 +2531,7 @@ xfs_alloc_space_available( > > > xfs_extlen_t reservation; /* blocks that are still reserved */ > > > int available; > > > xfs_extlen_t agflcount; > > > + xfs_extlen_t minleft; > > > if (flags & XFS_ALLOC_FLAG_FREEING) > > > return true; > > > @@ -2533,10 +2548,22 @@ xfs_alloc_space_available( > > > * Do we have enough free space remaining for the allocation? Don't > > > * account extra agfl blocks because we are about to defer free them, > > > * making them unavailable until the current transaction commits. > > > + * > > > + * If minleft is set, this allocation might cause an allocbt split that > > > + * increases the AGFL minimum for the next allocation in the > > > + * transaction. Reserve that space from the available block count > > > + * (without prematurely growing the AGFL) to prevent the subsequent > > > + * allocation from failing due to an increased min_free requirement. > > > */ > > > + minleft = args->minleft; > > > + if (minleft) { > > > + minleft += xfs_alloc_min_freelist_minleft(args->mp, pag) - > > > + min_free; > > > + } > > > + > > > agflcount = min_t(xfs_extlen_t, pag->pagf_flcount, min_free); > > > available = (int)(pag->pagf_freeblks + agflcount - > > > - reservation - min_free - args->minleft); > > > + reservation - min_free - minleft); > > > if (available < (int)max(args->total, alloc_len)) > > > return false; > > > diff --git a/fs/xfs/libxfs/xfs_alloc.h b/fs/xfs/libxfs/xfs_alloc.h > > > index 50ef79a1ed41..026b61a63994 100644 > > > --- a/fs/xfs/libxfs/xfs_alloc.h > > > +++ b/fs/xfs/libxfs/xfs_alloc.h > > > @@ -73,6 +73,8 @@ xfs_extlen_t xfs_alloc_longest_free_extent(struct xfs_perag *pag, > > > xfs_extlen_t need, xfs_extlen_t reserved); > > > unsigned int xfs_alloc_min_freelist(struct xfs_mount *mp, > > > struct xfs_perag *pag); > > > +unsigned int xfs_alloc_min_freelist_minleft(struct xfs_mount *mp, > > > + struct xfs_perag *pag); > > > int xfs_alloc_get_freelist(struct xfs_perag *pag, struct xfs_trans *tp, > > > struct xfs_buf *agfbp, xfs_agblock_t *bnop, int btreeblk); > > > int xfs_alloc_put_freelist(struct xfs_perag *pag, struct xfs_trans *tp, > > > diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c > > > index d64defeda645..f396df864cf4 100644 > > > --- a/fs/xfs/libxfs/xfs_bmap.c > > > +++ b/fs/xfs/libxfs/xfs_bmap.c > > > @@ -3160,7 +3160,7 @@ xfs_bmap_longest_free_extent( > > > } > > > > I intended to add a comment here but lost track. I.e., something like: > > > > /* > > * Use the minleft freelist helper because minleft can be set for bmbt > > * allocs. If we don't factor it in here, the alloc can be sized > > * incorrectly and fail. > > */ > > > > I'll wait for any further feedback before reposting. > > > > Brian > > > > > longest = xfs_alloc_longest_free_extent(pag, > > > - xfs_alloc_min_freelist(pag_mount(pag), pag), > > > + xfs_alloc_min_freelist_minleft(pag_mount(pag), pag), > > > xfs_ag_resv_needed(pag, XFS_AG_RESV_NONE)); > > > if (*blen < longest) > > > *blen = longest; > > > -- > > > 2.55.0 > > > > > > > > > thank you for the comment clarification. > I was confused by that change. > > Could you CC stable. We saw a version of this problem in Linux 5.15 > Sure, though I'm not sure how far back this will apply cleanly. Curious.. was the instance you saw also an inobt alloc failure leading to dirty trans cancel and shutdown, or something different but similar? Brian ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [External] : Re: [PATCH v2 3/3] xfs: incorporate increased AGFL min requirement for minleft allocs 2026-08-14 14:47 ` Brian Foster @ 2026-08-14 15:18 ` Mark Tinguely 2026-08-14 16:24 ` Brian Foster 0 siblings, 1 reply; 9+ messages in thread From: Mark Tinguely @ 2026-08-14 15:18 UTC (permalink / raw) To: Brian Foster; +Cc: linux-xfs, Matt Fleming On 8/14/26 9:47 AM, Brian Foster wrote: > On Fri, Aug 14, 2026 at 09: 15: 15AM -0500, Mark Tinguely wrote: > On 8/14/26 8: 57 AM, Brian Foster wrote: > > On Fri, Aug 14, 2026 at 09: 22: 39AM -0400, Brian Foster wrote: > > > Matt Fleming reports a filesystem shutdown due > > > On Fri, Aug 14, 2026 at 09:15:15AM -0500, Mark Tinguely wrote: >> On 8/14/26 8:57 AM, Brian Foster wrote: >> > On Fri, Aug 14, 2026 at 09:22:39AM -0400, Brian Foster wrote: >> > > Matt Fleming reports a filesystem shutdown due to inobt block >> > > allocation failure during sparse chunk allocation. Inode creation (deleted text) >> >> >> thank you for the comment clarification. >> I was confused by that change. >> >> Could you CC stable. We saw a version of this problem in Linux 5.15 >> > > Sure, though I'm not sure how far back this will apply cleanly. > > Curious.. was the instance you saw also an inobt alloc failure leading > to dirty trans cancel and shutdown, or something different but similar? > > Brian > yes the v5.15 problem was a similar sparse inode chunk / inode btree insert failure. I admit that I did not have the numbers to realize why the inode btree insert failed. although this is a fundamental allocation flaw, the sparse inode chunk allocation seems to be the just right/wrong conditions to trigger it. Fixes: 5419040fc0f3a xfs: introduce inode record hole mask for sparse inode chunks ?? thanks for the fix Mark Tinguely ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [External] : Re: [PATCH v2 3/3] xfs: incorporate increased AGFL min requirement for minleft allocs 2026-08-14 15:18 ` Mark Tinguely @ 2026-08-14 16:24 ` Brian Foster 0 siblings, 0 replies; 9+ messages in thread From: Brian Foster @ 2026-08-14 16:24 UTC (permalink / raw) To: Mark Tinguely; +Cc: linux-xfs, Matt Fleming On Fri, Aug 14, 2026 at 10:18:15AM -0500, Mark Tinguely wrote: > On 8/14/26 9:47 AM, Brian Foster wrote: > > On Fri, Aug 14, 2026 at 09: 15: 15AM -0500, Mark Tinguely wrote: > On 8/14/26 8: 57 AM, Brian Foster wrote: > > On Fri, Aug 14, 2026 at 09: 22: 39AM -0400, Brian Foster wrote: > > > Matt Fleming reports a filesystem shutdown due > > > > > > On Fri, Aug 14, 2026 at 09:15:15AM -0500, Mark Tinguely wrote: > > > On 8/14/26 8:57 AM, Brian Foster wrote: > > > > On Fri, Aug 14, 2026 at 09:22:39AM -0400, Brian Foster wrote: > > > > > Matt Fleming reports a filesystem shutdown due to inobt block > > > > > allocation failure during sparse chunk allocation. Inode creation > > > (deleted text) > > > > > > > > > > thank you for the comment clarification. > > > I was confused by that change. > > > > > > Could you CC stable. We saw a version of this problem in Linux 5.15 > > > > > > > Sure, though I'm not sure how far back this will apply cleanly. > > > > Curious.. was the instance you saw also an inobt alloc failure leading > > to dirty trans cancel and shutdown, or something different but similar? > > > > Brian > > > > yes the v5.15 problem was a similar sparse inode chunk / inode btree insert failure. > I admit that I did not have the numbers to realize why the inode btree insert failed. > Ok. > although this is a fundamental allocation flaw, the sparse inode chunk allocation > seems to be the just right/wrong conditions to trigger it. > I think that's probably right, but I had the Fixes: tag on v1 and dropped it here just because I think it's kind of a tenuous connection now. Re: the discussion on v1, presumably we could have the same thing happen with bmap allocs and just not see the shutdown because those allocs are usually more flexible and can fall back to different AGs. Regardless, wrt stable I think it would be reasonable to target back to the release that enabled sparse inodes (v4.2) since that's how this is reproduced in practice, so it probably doesn't make much difference either way. Brian > Fixes: 5419040fc0f3a xfs: introduce inode record hole mask for sparse inode chunks ?? > > thanks for the fix > > Mark Tinguely > > > ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-08-14 16:24 UTC | newest] Thread overview: 9+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-14 13:22 [PATCH v2 0/3] xfs: fix a couple sparse chunk alloc problems Brian Foster 2026-08-14 13:22 ` [PATCH v2 1/3] xfs: set minleft correctly for sparse chunk errortag allocation Brian Foster 2026-08-14 13:22 ` [PATCH v2 2/3] xfs: support additional levels in the agfl minimum calculation Brian Foster 2026-08-14 13:22 ` [PATCH v2 3/3] xfs: incorporate increased AGFL min requirement for minleft allocs Brian Foster 2026-08-14 13:57 ` Brian Foster 2026-08-14 14:15 ` [External] : " Mark Tinguely 2026-08-14 14:47 ` Brian Foster 2026-08-14 15:18 ` Mark Tinguely 2026-08-14 16:24 ` Brian Foster
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.