From: Zygo Blaxell Date: Fri, 14 Aug 2026 01:12:02 +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=421facb330215d7edff8966f332be5f3ea063dec;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 570d45556b5e0..b358162180441 100644 --- a/fs/btrfs/space-info.c +++ b/fs/btrfs/space-info.c @@ -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; }