]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: admit data reservations by whole stripes
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 11 Sep 2026 22:25:09 +0000 (18:25 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 21:40:06 +0000 (17:40 -0400)
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

index cc5765172ee0bbe575bfb26bb2b2a4b183bd8254..96d370a9179abafac1479e5cfa0e05b1763dedef 100644 (file)
@@ -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