From: Zygo Blaxell Date: Sat, 5 Sep 2026 18:50:03 +0000 (-0400) Subject: btrfs: stripe_alloc: charge relocation a margin per reservation, not per extent X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7de6ec8aae1ea31307f21becaefb46f7059ec79d;p=linux btrfs: stripe_alloc: charge relocation a margin per reservation, not per extent The stripe margin is one full stripe per outstanding extent, collateral for the tails a commit traps under delalloc that has not been placed yet. The data relocation inode charged it too, for extents that are already allocated: relocating a group of 16-48K files inflated bytes_stripe_margin to 150 MB, more than the claimable supply, so the next cluster reservation failed at once and the reclaim trigger retried the same six groups 130 times in 15 minutes while the margin swung between zero and 150 MB with nothing dirty. Do not charge the per-extent margin for the data relocation root. Instead, when a relocation reservation is checked against the whole-stripe supply, round it up to whole stripes and add one stripe of collateral, which is what relocation's own open runs can lose at the next commit. Assisted-by: Claude:claude-fable-5 --- diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c index 2e802b6851560..9fb9250ec7fb3 100644 --- a/fs/btrfs/space-info.c +++ b/fs/btrfs/space-info.c @@ -432,6 +432,12 @@ void btrfs_stripe_margin_mod(struct btrfs_inode *inode, int mod) if (!unit || !sinfo || mod == 0) return; + /* + * Relocation's extents are already placed; its reservations are + * charged a margin of their own in stripe_claimable_admit(). + */ + if (btrfs_is_data_reloc_root(inode->root)) + return; if (btrfs_test_opt(fs_info, STRIPE_ALLOC)) { /* charges and releases both live */ } else if (mod > 0 || READ_ONCE(sinfo->bytes_stripe_margin) == 0) { @@ -608,14 +614,22 @@ static void remove_ticket(struct btrfs_space_info *space_info, * 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) +static bool stripe_claimable_admit(struct btrfs_space_info *space_info, u64 bytes, + bool reloc) { struct btrfs_fs_info *fs_info = space_info->fs_info; + const u64 unit = READ_ONCE(fs_info->stripe_margin_unit); if (!btrfs_test_opt(fs_info, STRIPE_ALLOC) || - !(space_info->flags & BTRFS_BLOCK_GROUP_DATA) || - !READ_ONCE(fs_info->stripe_margin_unit)) + !(space_info->flags & BTRFS_BLOCK_GROUP_DATA) || !unit) return true; + /* + * Relocation carries no per-extent margin (its extents are already + * 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; } @@ -643,7 +657,7 @@ again: 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)) { + stripe_claimable_admit(space_info, ticket->bytes, false)) { btrfs_space_info_update_bytes_may_use(space_info, ticket->bytes); remove_ticket(space_info, ticket); ticket->bytes = 0; @@ -1889,7 +1903,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)) && - stripe_claimable_admit(space_info, orig_bytes)) { + stripe_claimable_admit(space_info, orig_bytes, + flush == BTRFS_RESERVE_FLUSH_DATA_RELOC)) { btrfs_space_info_update_bytes_may_use(space_info, orig_bytes); ret = 0; }