From ce9f0599f1959eeb3efbbe41e99b83b2fe2b03b9 Mon Sep 17 00:00:00 2001 From: Zygo Blaxell Date: Fri, 11 Sep 2026 18:25:09 -0400 Subject: [PATCH] btrfs: stripe_alloc: admit data reservations by whole stripes An allocation of N bytes that has to open a stripe run claims round_up(N, full stripe) whole stripes. A delalloc write is admitted with a full-stripe margin per outstanding extent, which covers that roundup for itself, and relocation is admitted for round_up(N) + 1 stripe. A preallocation was admitted for N alone: fallocate() has no outstanding extent and holds no margin, so each one short of a stripe took up to a stripe more from the whole-stripe supply than the gate had charged. The supply behind writes admitted earlier shrank by that much and at the fill edge their claims found nothing. With the stranded-stripe accounting and the relocation margin in place, the failure-time dump on a degraded fsstress fill showed exactly this: every failed write had bytes_stripe_claimable 0, bytes_may_use equal to its own size, one stripe of margin per outstanding extent, nothing stranded, no group read-only and no relocation group dedicated -- the gate's arithmetic was consistent and still the stripes were gone, with a preallocation's reservation in flight in the dump. Charge every non-relocation admission the roundup to whole stripes. The excess is not held past admission (a preallocation allocates in the same call), so a concurrent admission in that window can still overrun by a stripe; holding it would need the reservation to carry the roundup until the allocation lands, a later refinement. Assisted-by: Claude:claude-fable-5 --- fs/btrfs/space-info.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c index cc5765172ee0b..96d370a9179ab 100644 --- a/fs/btrfs/space-info.c +++ b/fs/btrfs/space-info.c @@ -654,6 +654,20 @@ static bool stripe_claimable_admit(struct btrfs_space_info *space_info, u64 byte } else { /* ...and that group is out of reach for everyone else */ supply -= min(supply, dedicated); + /* + * An allocation of @bytes that opens a run claims + * round_up(bytes, unit) whole stripes. A delalloc write's + * margin (below) covers that for itself, but a + * preallocation holds no margin -- it has no outstanding + * extent -- and was admitted for its byte size alone, so + * each fallocate() short of a stripe took up to a stripe + * more than the gate had charged, out of the supply behind + * writes already admitted: at the fill edge those writes + * then found no stripe and were dropped. Charge the + * roundup. (The excess is not held past admission; a + * preallocation allocates in the same call.) + */ + bytes = round_up(bytes, unit); } /* * Every outstanding data extent holds a whole-stripe margin -- 2.53.0