]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_meta: do not steal from the global reserve below the relocation reserve
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sun, 6 Sep 2026 05:14:43 +0000 (01:14 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 21:40:05 +0000 (17:40 -0400)
With the whole-stripe reserve in place the fill stopped cleanly at the
edge: 374 MiB claimable, the reserve plus margin plus the outstanding
reservations, no canary, no abort.  Then the test deleted half of its
tiny files, and every unlink that the gate refused came back through
the global reserve steal, which does not look at stripes.  Deleting an
inline file is pure COW churn under stripe_meta: it frees no stripe,
since the leaf it lived in stays, and each transaction of unlinks takes
fresh stripes.  Metadata used did not move by a megabyte while the
claimable supply went from 374 MiB to zero, and the next commit aborted
with the canary ("claimable 0 open 1490944 trapped 1506082816").

Refuse the steal once the claimable supply is down to the relocation
reserve plus the claim margin.  The unlink fails with ENOSPC, which is
recoverable, instead of the abort, which is not, and the reserve stays
with the background relocation that is the only thing that turns
trapped space back into whole stripes.

Assisted-by: Claude:claude-fable-5
fs/btrfs/space-info.c

index f7fa2b6dd80e06b90d6c2bf7438046a2667c530f..73f9d3c939c0cac84fa81b753ac2520359dbbcd4 100644 (file)
@@ -1196,6 +1196,23 @@ static bool steal_from_global_rsv(struct btrfs_fs_info *fs_info,
        if (global_rsv->space_info != space_info)
                return false;
 
+       /*
+        * stripe_meta: the global reserve is refilled by fiat at every
+        * commit, so its bytes are only as real as the whole stripes behind
+        * them.  Once the claimable supply is down to the relocation reserve,
+        * a stolen reservation admits a transaction that consumes fresh
+        * stripes through COW while everything it frees comes back trapped
+        * (an unlink of an inline file returns nothing at all), until a tree
+        * block finds no stripe and the commit aborts.  Refuse the steal:
+        * -ENOSPC from an unlink is recoverable, an abort is not, and the
+        * reserve stays with the background relocation that will make room.
+        */
+       if (space_info->bytes_stripe_reserve &&
+           space_info->bytes_stripe_claimable <
+           space_info->bytes_stripe_reserve + space_info->bytes_stripe_margin +
+           ticket->bytes)
+               return false;
+
        spin_lock(&global_rsv->lock);
        min_bytes = mult_perc(global_rsv->size, 10);
        if (global_rsv->reserved < min_bytes + ticket->bytes) {