From 0ef5fa77a2825788e978a37bb5b6bf2aa1b90133 Mon Sep 17 00:00:00 2001 From: Zygo Blaxell Date: Sat, 5 Sep 2026 23:40:50 -0400 Subject: [PATCH] btrfs: stripe_alloc: keep the relocation group's stripes out of the admission supply do_allocation_stripe() dedicates one block group to data relocation (fs_info->data_reloc_bg, as the zoned allocator does): while a relocation runs, its allocations go only there and everyone else's skip it, until the relocation finishes or the group fills. The admission gate did not know: it counted that group's claimable stripes in the supply for ordinary writes. At the fill edge with background reclaim running, the dedicated group was the only one with claimable stripes left, ordinary writes were admitted against them, and their writebacks were dropped: stripe_alloc DROP DUMP: sinfo may_use 3837952 claimable 561053696 ... bg 7048265728 ... claimable 561053696 ... runs open <- data_reloc_bg every other group: claimable 0 Track the dedicated group's claimable bytes in the space_info (bytes_stripe_claimable_reloc: set when a group is dedicated, cleared when the dedication is released or the group is removed, followed by the per-group claimable deltas and re-summed by the commit rescan) and admit ordinary reservations against the supply minus that amount. Relocation itself is admitted against the whole supply, dedicated group included. Confining it to the dedicated group's bytes was tried first and refused relocation as soon as that group -- the first one the allocator visits, often holding only a couple of free stripes -- ran short of a reservation's whole stripes plus one, while hundreds of megabytes of whole stripes sat in other groups: the allocator would have dropped the dedication and moved on had it been asked, but the gate failed each queued group with ENOSPC within fifteen milliseconds and the reclaim worker retried every thirty seconds, with no relocation ever completing. bytes_may_use is one counter for both sides, so the sum admitted still fits in the supply. Assisted-by: Claude:claude-fable-5 Assisted-by: Claude:claude-fable-5-1 --- fs/btrfs/block-group.c | 21 ++++++++++++++++++++- fs/btrfs/extent-tree.c | 12 +++++++++++- fs/btrfs/free-space-cache.c | 8 ++++++++ fs/btrfs/space-info.c | 25 ++++++++++++++++++++++--- fs/btrfs/space-info.h | 5 +++++ 5 files changed, 66 insertions(+), 5 deletions(-) diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index fe987c3542b37..cf8a850dad778 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -2603,6 +2603,7 @@ void btrfs_scan_stripe_unusable(struct btrfs_fs_info *fs_info, bool force) list_for_each_entry(sinfo, &fs_info->space_info, list) { u64 total = 0; u64 total_claimable = 0; + u64 total_claimable_reloc = 0; int raid; if (!(sinfo->flags & BTRFS_BLOCK_GROUP_DATA)) @@ -2638,6 +2639,9 @@ void btrfs_scan_stripe_unusable(struct btrfs_fs_info *fs_info, bool force) total += READ_ONCE(bg->stripe_unusable); total_claimable += READ_ONCE(bg->stripe_claimable); + if (bg->start == READ_ONCE(fs_info->data_reloc_bg)) + total_claimable_reloc += + READ_ONCE(bg->stripe_claimable); } } } @@ -2646,6 +2650,7 @@ void btrfs_scan_stripe_unusable(struct btrfs_fs_info *fs_info, bool force) spin_lock(&sinfo->lock); sinfo->bytes_stripe_unusable = total; sinfo->bytes_stripe_claimable = total_claimable; + sinfo->bytes_stripe_claimable_reloc = total_claimable_reloc; /* * The rescan usually lowers the counter (mid-transaction * incremental adds overcount conservatively); admission @@ -2748,10 +2753,19 @@ void btrfs_clear_data_reloc_bg(struct btrfs_block_group *bg) { struct btrfs_fs_info *fs_info = bg->fs_info; + bool cleared = false; + spin_lock(&fs_info->relocation_bg_lock); - if (fs_info->data_reloc_bg == bg->start) + if (fs_info->data_reloc_bg == bg->start) { fs_info->data_reloc_bg = 0; + cleared = true; + } spin_unlock(&fs_info->relocation_bg_lock); + if (cleared && bg->space_info) { + spin_lock(&bg->space_info->lock); + bg->space_info->bytes_stripe_claimable_reloc = 0; + spin_unlock(&bg->space_info->lock); + } } /* @@ -2775,6 +2789,11 @@ void btrfs_release_data_reloc_bg(struct btrfs_fs_info *fs_info) bg = btrfs_lookup_block_group(fs_info, bytenr); if (!bg) return; + if (bg->space_info) { + spin_lock(&bg->space_info->lock); + bg->space_info->bytes_stripe_claimable_reloc = 0; + spin_unlock(&bg->space_info->lock); + } btrfs_close_bg_open_stripes(bg); btrfs_put_block_group(bg); } diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c index a3317b732bd13..b64863ca066ac 100644 --- a/fs/btrfs/extent-tree.c +++ b/fs/btrfs/extent-tree.c @@ -4072,6 +4072,7 @@ static int do_allocation_stripe(struct btrfs_block_group *block_group, { struct btrfs_fs_info *fs_info = block_group->fs_info; enum btrfs_stripe_run_class class = BTRFS_STRIPE_RUN_COW; + bool dedicated_now = false; u64 available = 0; u64 offset; bool skip = false; @@ -4111,14 +4112,23 @@ static int do_allocation_stripe(struct btrfs_block_group *block_group, spin_lock(&fs_info->relocation_bg_lock); if (ffe_ctl->for_data_reloc) { class = BTRFS_STRIPE_RUN_RELOC; - if (!fs_info->data_reloc_bg) + if (!fs_info->data_reloc_bg) { fs_info->data_reloc_bg = block_group->start; + dedicated_now = true; + } } if (fs_info->data_reloc_bg && (ffe_ctl->for_data_reloc != (block_group->start == fs_info->data_reloc_bg))) skip = true; spin_unlock(&fs_info->relocation_bg_lock); + if (dedicated_now && block_group->space_info) { + /* its claimable stripes now belong to relocation alone */ + spin_lock(&block_group->space_info->lock); + block_group->space_info->bytes_stripe_claimable_reloc = + READ_ONCE(block_group->stripe_claimable); + spin_unlock(&block_group->space_info->lock); + } if (skip) return 1; diff --git a/fs/btrfs/free-space-cache.c b/fs/btrfs/free-space-cache.c index 36ea5029757d3..775a19a5816e1 100644 --- a/fs/btrfs/free-space-cache.c +++ b/fs/btrfs/free-space-cache.c @@ -2806,6 +2806,12 @@ static void stripe_claimable_mod(struct btrfs_block_group *bg, s64 delta) sinfo->bytes_stripe_claimable = 0; else btrfs_space_info_update_bytes_stripe_claimable(sinfo, delta); + if (bg->start == READ_ONCE(bg->fs_info->data_reloc_bg)) { + if (delta < 0 && sinfo->bytes_stripe_claimable_reloc < (u64)-delta) + sinfo->bytes_stripe_claimable_reloc = 0; + else + sinfo->bytes_stripe_claimable_reloc += delta; + } spin_unlock(&sinfo->lock); } @@ -3443,6 +3449,8 @@ void btrfs_block_group_disarm_stripe_unusable(struct btrfs_block_group *bg) -(s64)trapped); btrfs_space_info_update_bytes_stripe_claimable(bg->space_info, -(s64)claimable); + if (bg->start == READ_ONCE(bg->fs_info->data_reloc_bg)) + bg->space_info->bytes_stripe_claimable_reloc = 0; spin_unlock(&bg->space_info->lock); } } diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c index 6c8e6cf02bdc0..97f0e0c4372b0 100644 --- a/fs/btrfs/space-info.c +++ b/fs/btrfs/space-info.c @@ -628,9 +628,28 @@ static bool stripe_claimable_admit(struct btrfs_space_info *space_info, u64 byte * placed): charge it whole stripes plus one, the tail its own open * runs can lose at the next commit. */ - if (reloc) - bytes = round_up(bytes, unit) + unit; - return space_info->bytes_may_use + bytes <= space_info->bytes_stripe_claimable; + { + u64 supply = space_info->bytes_stripe_claimable; + const u64 dedicated = space_info->bytes_stripe_claimable_reloc; + + if (reloc) { + /* + * Against the whole supply, dedicated group included. + * Relocation prefers its dedicated group, but when + * that group runs dry the allocator drops the + * dedication and moves on to another group; confining + * admission to the dedicated group's claimable bytes + * refused relocation (ENOSPC after a handful of 16K + * clusters, retried every 30 seconds) while hundreds + * of megabytes of whole stripes sat in other groups. + */ + bytes = round_up(bytes, unit) + unit; + } else { + /* ...and that group is out of reach for everyone else */ + supply -= min(supply, dedicated); + } + return space_info->bytes_may_use + bytes <= supply; + } } /* diff --git a/fs/btrfs/space-info.h b/fs/btrfs/space-info.h index dbf349a04fe79..989b1f6ad5b0f 100644 --- a/fs/btrfs/space-info.h +++ b/fs/btrfs/space-info.h @@ -141,6 +141,11 @@ struct btrfs_space_info { cannot admit writes against it, and subtracted in statfs (from a different base -- the free space cache walk). */ + u64 bytes_stripe_claimable_reloc; /* the part of bytes_stripe_claimable + in the group dedicated to data + relocation (fs_info->data_reloc_bg): + out of reach for other allocations + while the dedication lasts */ u64 bytes_stripe_claimable; /* sum of armed groups' directly measured claimable whole-stripe bytes; see stripe_claimable in -- 2.53.0