From 8072ad7afd9d89cabdb6eb47becebff2e2addfb0 Mon Sep 17 00:00:00 2001 From: Zygo Blaxell Date: Sun, 6 Sep 2026 07:44:45 -0400 Subject: [PATCH] btrfs: stripe_meta: let the transaction machinery's own reservations draw on the reserve With the whole-stripe reserve counted as used for everyone but the relocation task, a committing transaction whose handle reserve runs dry mid-COW gets ENOSPC from btrfs_use_block_rsv(): its NO_FLUSH fallback reservation is refused as soon as the claimable supply is below the reserve, and the global reserve behind it has been refilled by fiat against stripes that do not exist. Seen on the delete phase of the metadata fill test with relocation running: claimable 163 MiB, reserve 352 MiB, delayed refs freeing the deleted leaves, and the extent tree COW that freeing needs aborted the transaction with ENOSPC while 163 MiB of whole stripes sat unused (no allocator canary: the allocator was never asked). The reserve exists to hold back user operations so relocation and the commit that follows it have room; it must not hold back the commit. Let NO_FLUSH, FLUSH_LIMIT (delayed refs, delayed inodes) and EMERGENCY reservations draw on it like relocation does. They are bounded and kernel-internal; user reservations (FLUSH_ALL, ALL_STEAL, EVICT, DATA) still stop at the reserve. Assisted-by: Claude:claude-fable-5 --- fs/btrfs/space-info.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c index 73f9d3c939c0c..829b1719e71a6 100644 --- a/fs/btrfs/space-info.c +++ b/fs/btrfs/space-info.c @@ -1906,7 +1906,19 @@ static int __reserve_bytes(struct btrfs_fs_info *fs_info, u64 used; int ret = -ENOSPC; bool pending_tickets; - const bool reloc = (READ_ONCE(fs_info->reloc_task) == current); + /* + * Who may draw on the stripe_meta reserve (bytes_stripe_reserve): + * the relocation task, which is what the reserve is for, and the + * transaction machinery's own bounded reservations -- NO_FLUSH from + * btrfs_use_block_rsv() when a handle's reserve runs dry mid-COW, + * FLUSH_LIMIT for delayed refs and delayed inodes, EMERGENCY -- which + * a committing transaction cannot fail without aborting. User + * operations (FLUSH_ALL, ALL_STEAL, EVICT, DATA) stay behind it. + */ + const bool reloc = (READ_ONCE(fs_info->reloc_task) == current) || + flush == BTRFS_RESERVE_NO_FLUSH || + flush == BTRFS_RESERVE_FLUSH_LIMIT || + flush == BTRFS_RESERVE_FLUSH_EMERGENCY; ASSERT(orig_bytes); /* -- 2.53.0