]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: gate data admission on claimable whole-stripe supply
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 14 Aug 2026 01:12:02 +0000 (21:12 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 21:40:03 +0000 (17:40 -0400)
Wire stripe_claimable into the DATA reservation path so write() returns -ENOSPC
when there is no fully-free whole stripe to place the write crash-safely --
matching statfs f_bavail, which already subtracts the trapped partial-stripe
space.  Add stripe_claimable_admit() and AND it into the two data-admit sites:
__reserve_bytes() and btrfs_try_granting_tickets() (the latter is required, or a
flushed data ticket would be granted by the plain used<=total rule, bypassing the
gate).  Scoped to raid56 stripe_alloc DATA (stripe_margin_unit != 0); metadata,
non-raid56 and zoned space_infos are unaffected.

The gate refuses at reservation time, before the range becomes delalloc and later
parks at writeback with nowhere to land -- so ENOSPC is clean and there is no
fill-edge parking collapse.

Phase 1 is pessimistic: it gates on bytes_stripe_claimable alone, which errs LOW,
so it can refuse a write that would have landed in a partially-open stripe
(over-refuses by the open remainder, healed by the commit rescan raising claimable
and the FLUSH_DATA ticket retry).  A later change adds bytes_stripe_open to the
bound for statfs-exact behavior.

Assisted-by: Claude:claude-opus-4-8
fs/btrfs/space-info.c

index 570d45556b5e01bb58ab29d25d2d16dc379c5881..b358162180441eb0c7969aabea00622911ccb2f8 100644 (file)
@@ -590,6 +590,35 @@ static void remove_ticket(struct btrfs_space_info *space_info,
        }
 }
 
+/*
+ * stripe_alloc DATA admission gate.  On a raid56 stripe_alloc DATA space_info a
+ * write can only be placed crash-safely in a fully-free whole stripe, so admit a
+ * data reservation only while the whole-stripe supply (bytes_stripe_claimable,
+ * which errs LOW now that the incremental positive credit is gone) covers all
+ * outstanding reservations plus this one.  This refuses at reservation time --
+ * before the range becomes delalloc and later parks at writeback with nowhere to
+ * land -- and makes write() -ENOSPC coincide with statfs f_bavail==0 (both on the
+ * whole-stripe bound).  Non-stripe_alloc / metadata / non-raid56 space_infos are
+ * unaffected: stripe_margin_unit is 0 unless a raid56 DATA bg exists.  Caller
+ * holds space_info->lock.
+ *
+ * Phase 1 is deliberately PESSIMISTIC: it gates on claimable alone, so it can
+ * refuse a write that would have landed in a partially-open stripe (over-refuses
+ * by the open remainder, healed by the commit rescan raising claimable + the
+ * FLUSH_DATA ticket retry).  A later phase adds bytes_stripe_open to the bound to
+ * match statfs exactly and recover that space.
+ */
+static bool stripe_claimable_admit(struct btrfs_space_info *space_info, u64 bytes)
+{
+       struct btrfs_fs_info *fs_info = space_info->fs_info;
+
+       if (!btrfs_test_opt(fs_info, STRIPE_ALLOC) ||
+           !(space_info->flags & BTRFS_BLOCK_GROUP_DATA) ||
+           !READ_ONCE(fs_info->stripe_margin_unit))
+               return true;
+       return space_info->bytes_may_use + bytes <= space_info->bytes_stripe_claimable;
+}
+
 /*
  * This is for space we already have accounted in space_info->bytes_may_use, so
  * basically when we're returning space from block_rsv's.
@@ -611,9 +640,10 @@ again:
                ticket = list_first_entry(head, struct reserve_ticket, list);
 
                /* Check and see if our ticket can be satisfied now. */
-               if ((used + ticket->bytes <= space_info->total_bytes) ||
-                   btrfs_can_overcommit(fs_info, space_info, ticket->bytes,
-                                        flush)) {
+               if (((used + ticket->bytes <= space_info->total_bytes) ||
+                    btrfs_can_overcommit(fs_info, space_info, ticket->bytes,
+                                         flush)) &&
+                   stripe_claimable_admit(space_info, ticket->bytes)) {
                        btrfs_space_info_update_bytes_may_use(space_info, ticket->bytes);
                        remove_ticket(space_info, ticket);
                        ticket->bytes = 0;
@@ -1851,7 +1881,8 @@ static int __reserve_bytes(struct btrfs_fs_info *fs_info,
         */
        if (!pending_tickets &&
            ((used + orig_bytes <= space_info->total_bytes) ||
-            btrfs_can_overcommit(fs_info, space_info, orig_bytes, flush))) {
+            btrfs_can_overcommit(fs_info, space_info, orig_bytes, flush)) &&
+           stripe_claimable_admit(space_info, orig_bytes)) {
                btrfs_space_info_update_bytes_may_use(space_info, orig_bytes);
                ret = 0;
        }