]> git.hungrycats.org Git - linux/commit
btrfs: stripe_meta: allocate tree blocks by whole stripes
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sun, 9 Aug 2026 22:05:48 +0000 (18:05 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 21:40:05 +0000 (17:40 -0400)
commita8bbf889b9627ec597686ce3f4703dcc02a18681
tree3d88ed17dd4e83d299b97cf52c47c69e93e0e0b2
parentefca42cb6bfea91b57ea9ec21f067fe360209d46
btrfs: stripe_meta: allocate tree blocks by whole stripes

Metadata gets the placement rule data already has: tree blocks are handed
out from runs of fully free stripes, and a stripe is never revisited once
the transaction that filled it has written it.  No separate mount option: the
chunk profile already decides it.  raid56 metadata has the same write hole
as raid56 data, so it gets the same protection at the same time, and
metadata on any other profile has no write hole to close -- the policy's
RAID56_MASK test simply does not match it, so nothing changes there.

It does cost a stranded stripe tail per commit, and a metadata allocation
that fails aborts the transaction rather than failing one write.  Those
are the price of covering raid56 metadata at all rather than a reason to
make it opt-in separately: a filesystem unwilling to pay them does not
want raid56 metadata, and converting it to raid1c3/raid1c4 removes both
the cost and the write hole.

Only system chunks are left uncovered: btrfs_is_stripe_meta_bg() tests the
METADATA flag and a system chunk does not carry it, so a raid56 system
chunk keeps the legacy read-modify-write.  The mount-time warning is
narrowed to say exactly that instead of claiming metadata is uncovered,
which it no longer is.

The machinery is the data machinery, including the per-sector liveness
map the previous patch gave it.  Metadata needed three things of its own:

 - A completion report.  A run drains when every byte allocated from it
   has been accounted for, and metadata had no equivalent of ordered
   extent completion, so its runs could never drain -- the deadlock that
   mixed block groups hit.  end_bbio_meta_write() now reports, and
   btrfs_open_stripe_write_abandoned() covers a tree block freed before
   it was ever written (COWed twice in one transaction, say).

 - A two-phase closure.  btrfs_retire_open_stripes() runs before the
   commit writes tree blocks, so it skips metadata runs; they are closed
   and drained afterwards by btrfs_retire_meta_stripes(), once every block
   is on disk.  Closing earlier would strand blocks the commit has not
   allocated yet, draining earlier would wait for writes it has not
   issued.  Metadata runs are also freed there rather than when their
   counter reaches zero: a stripe's columns can be written by separate
   rbios, and freeing on the first completion leaves the second with no
   run to pad or merge against.

 - Whole stripes per writeback pass.  The opportunistic writeback passes
   do not know about stripes and will happily write three tree blocks now
   and five later; btrfs_defer_stripe_meta_write() leaves the blocks
   dirty for the commit (or a WB_SYNC_ALL waiter) instead, the same shape
   as the zoned write-pointer deferral.

Two free paths bypass the usual completion reporting and must report the
block abandoned themselves: btrfs_free_tree_block() can send a
same-transaction unwritten block through the delayed-ref machinery when
check_ref_cleanup() finds its ref head already processed, and
clean_log_buffer() discards unwritten log tree blocks via
btrfs_pin_reserved_extent() without ever visiting btrfs_free_tree_block().
Either miss leaves the block counted as inflight forever: the run never
drains, the block group reference never drops, and unmount asserts.  Both
were found by the dm-log-writes replay matrix, whose crash-recovery mounts
exercise both paths routinely.

Assisted-by: Claude:claude-fable-5
fs/btrfs/block-group.c
fs/btrfs/block-group.h
fs/btrfs/extent-tree.c
fs/btrfs/extent_io.c
fs/btrfs/free-space-cache.c
fs/btrfs/transaction.c
fs/btrfs/tree-log.c