]> git.hungrycats.org Git - linux/commit
btrfs: stripe_alloc: count claimable whole-stripe supply directly
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sun, 9 Aug 2026 20:04:21 +0000 (16:04 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 18 Sep 2026 21:36:24 +0000 (17:36 -0400)
commitcb2d11760cbd9151461b15a2ead7236ff5032f2b
tree2d5f38aaeb20d41efc515aab65ea864ace11d7fc
parent0d6a25da505735201b88d92da430c77b68684554
btrfs: stripe_alloc: count claimable whole-stripe supply directly

Data reservations are admitted against arithmetic -- free space minus
trapped fragments minus open-run remainders minus a per-extent margin --
and under sustained near-full churn the arithmetic and the claim rule
disagree for long enough that admitted buffered writes reach writeback
with nothing claimable left: measured, ~5.2-5.5k reserved writebacks
dropped per fsstress churn run, silently for anything not waiting on
fsync.  Stock refuses the same write()s up front.  Nothing between
"durable by fsync" and "refused by write()" is acceptable when an
operator accident fills the disk.

First step, accounting only: measure the constraint instead of deriving
it.  bg->stripe_claimable counts bytes of fully free whole stripes --
exactly what btrfs_claim_free_stripe_run() can take, the complement of
stripe_unusable within each stripe.  The commit rescan derives it from
the same pass that computes trapped bytes (free minus trapped) and is
the sole upward correction; incremental maintenance under
ctl->tree_lock only ever DEBITS -- removals round their decrement OUT
to every touched stripe, claims subtract exactly what they took.  The
counter can therefore only under-count between commits, never
over-count.

Crediting freed whole stripes incrementally on the add side is
deliberately not done, having been tried and dropped.  Several add
paths re-add free space that a low-level remove never de-credited --
the async discard trim (unlink_free_space / bitmap_clear_bits, then
do_trimming's re-add through __btrfs_add_free_space) and
btrfs_remove_free_space's middle-split tail re-add -- so crediting on
add double-counts and drives the counter ABOVE the authoritative scan.
That is the dangerous direction: once admission gates on this counter,
an over-count admits reservations against phantom supply that writeback
then drops, while an under-count is an early, clean write()-time
ENOSPC.  Debit-only makes over-counting structurally impossible,
whatever a future re-add path forgets to de-credit.  (Reproduced with
compress+autodefrag+discard=async on a legacy-converted raid5
filesystem, incremental ~= 2x scanned; credit_return attribution
confirmed the phantom entered through do_trimming's re-add, not the
open-stripe allocator returns.)

A WARN_RATELIMIT at the rescan catches the dangerous direction anyway
(incremental above scanned), which under a debit-only rule means a
missed consumption site, and localizes it; the clamp to the scanned
value keeps every rescan authoritative regardless.  Rate limited rather
than _ONCE because such a gap recurs on every commit, and reporting
only the first hit hides that it is ongoing.

The space_info aggregate bytes_stripe_claimable follows the
stripe_unusable pattern (incremental between commits, re-totaled from
armed groups at the rescan), shows in the ENOSPC dump and in sysfs.
No admission change yet; that comes once the counter proves accurate
under the fill, churn, balance and reclaim suites.  The by-size
fast-fail in the claim keeps its early exit: the point of measuring is
to make such inputs trustworthy, not to search harder around them.

Data block groups only for now; raid56 metadata and mixed block groups
join when the data counter has settled.

Assisted-by: Claude:claude-fable-5
fs/btrfs/block-group.c
fs/btrfs/block-group.h
fs/btrfs/free-space-cache.c
fs/btrfs/space-info.c
fs/btrfs/space-info.h
fs/btrfs/sysfs.c
include/trace/events/btrfs.h