From: Zygo Blaxell Date: Fri, 14 Aug 2026 01:12:04 +0000 (-0400) Subject: btrfs: stripe_alloc: gate data admission on claimable whole-stripe supply X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fheads%2Ftopics%2Fstripe-alloc;p=linux btrfs: stripe_alloc: gate data admission on claimable whole-stripe supply 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 --- diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c index 74a1fc7906b9b..66f652f0bbc75 100644 --- a/fs/btrfs/space-info.c +++ b/fs/btrfs/space-info.c @@ -649,6 +649,35 @@ static void remove_ticket(struct btrfs_space_info *space_info, spin_unlock(&ticket->lock); } +/* + * 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. @@ -671,8 +700,9 @@ again: used_after = used + ticket->bytes; /* Check and see if our ticket can be satisfied now. */ - if (used_after <= space_info->total_bytes || - can_overcommit(space_info, used, ticket->bytes, flush)) { + if ((used_after <= space_info->total_bytes || + can_overcommit(space_info, used, 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, 0); space_info->tickets_id++; @@ -1905,7 +1935,8 @@ static int reserve_bytes(struct btrfs_space_info *space_info, u64 orig_bytes, */ if (!pending_tickets && ((used + orig_bytes <= space_info->total_bytes) || - can_overcommit(space_info, used, orig_bytes, flush))) { + can_overcommit(space_info, used, orig_bytes, flush)) && + stripe_claimable_admit(space_info, orig_bytes)) { btrfs_space_info_update_bytes_may_use(space_info, orig_bytes); ret = 0; }