]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: keep the admitted bytes placeable when a group goes read-only
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sat, 5 Sep 2026 12:17:26 +0000 (08:17 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 21:40:05 +0000 (17:40 -0400)
inc_block_group_ro() checks that the rest of the space_info can absorb
the group's free space in bytes.  Under stripe_alloc the data admission
gate promised writers whole stripes: bytes_may_use plus the held margin
against bytes_stripe_claimable.  Making a group read-only removes its
claimable stripes from that supply after the promise was made, so with
the reclaim worker relocating a group at the fill edge, admitted
writebacks found no stripe and were dropped:

  allocation failed flags 129, wanted 8192 ...
  space_info DATA has 450560 free, is full ... block group ... [readonly]

Refuse the read-only transition while the admitted bytes would no
longer fit in the claimable supply without this group.  Scrub, balance
and reclaim retry later; this is the fail-early direction and only bites
transiently at the edge, since bytes_may_use drains as writeback
completes.

Assisted-by: Claude:claude-fable-5
fs/btrfs/block-group.c

index 56cc847ca62affd46a4f4c36d5945bb25cd2d539..50a19d3fc9377a6d77c4eae146c1c8408d961fd4 100644 (file)
@@ -3796,6 +3796,19 @@ static int inc_block_group_ro(struct btrfs_block_group *cache, bool force)
                 */
                if (sinfo_used - trapped + num_bytes <= sinfo->total_bytes)
                        ret = 0;
+
+               /*
+                * The stripe_alloc gate admitted writers against whole
+                * stripes (bytes_may_use plus the held margin must fit in
+                * bytes_stripe_claimable).  Going read-only removes this
+                * group's claimable stripes from that supply, so refuse while
+                * the bytes already admitted would no longer be placeable:
+                * otherwise their writebacks fail and the data is dropped.
+                */
+               if (!ret && READ_ONCE(cache->stripe_unusable_ready) &&
+                   sinfo->bytes_may_use + sinfo->bytes_stripe_margin >
+                   sinfo->bytes_stripe_claimable - cache->stripe_claimable)
+                       ret = -ENOSPC;
        } else {
                /*
                 * We overcommit metadata, so we need to do the