From 499ca8fdf310bdf6f6a7119cd5c50adce2d6542f Mon Sep 17 00:00:00 2001 From: Zygo Blaxell Date: Mon, 7 Sep 2026 07:33:59 -0400 Subject: [PATCH] btrfs: stripe_alloc: give a queued stripe group a chunk to relocate into when the supply is short A relocation data reservation never takes a ticket, so the flush state machine never runs for it, and with it goes the chunk allocation every other data reservation gets when its space_info is full. That did not show while other groups still had whole stripes. Once the reclaim worker had emptied and removed five of seven data groups, the two left were the trapped ones it was queued to move next, their claimable supply was under a megabyte, and every relocation failed with ENOSPC while 2.6 GiB of the devices sat unallocated. Have the reclaim worker allocate one chunk before it relocates a stripe group whose live bytes exceed the claimable supply of the other groups. An earlier version did this from the reservation path instead, for any refused relocation reservation; that let a live device shrink -- which relocates into space it does not have and is meant to fail cleanly at that reservation -- grow the data space_info chunk by chunk until the raid1 metadata had nowhere to go and the relocation aborted the transaction in merge_reloc_roots() (6.18 acceptance suite T6). The worker is the one caller that should be making room. Assisted-by: Claude:claude-fable-5 --- fs/btrfs/block-group.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index 50a19d3fc9377..fe987c3542b37 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -4309,6 +4309,35 @@ void btrfs_reclaim_bgs_work(struct work_struct *work) space_info = bg->space_info; spin_unlock(&fs_info->unused_bgs_lock); + /* + * A stripe_alloc / stripe_meta group's live extents can only + * move into whole free stripes, and relocation never waits for + * them. Once the worker has emptied and removed enough groups, + * the ones left are the trapped ones it was queued to move next + * with next to nothing claimable, while gigabytes may sit + * unallocated: give it a chunk to relocate into, once per + * attempt. Here rather than in the reservation path so that a + * device shrink, which relocates without room by design, keeps + * failing cleanly instead of growing into space it cannot use. + */ + if (btrfs_is_stripe_alloc_bg(bg) || btrfs_is_stripe_meta_bg(bg)) { + u64 elsewhere = READ_ONCE(space_info->bytes_stripe_claimable); + + elsewhere -= min(elsewhere, READ_ONCE(bg->stripe_claimable)); + if (elsewhere < READ_ONCE(bg->used)) { + struct btrfs_trans_handle *trans; + + trans = btrfs_join_transaction(fs_info->tree_root); + if (!IS_ERR(trans)) { + btrfs_chunk_alloc(trans, space_info, + btrfs_get_alloc_profile(fs_info, + space_info->flags), + CHUNK_ALLOC_FORCE); + btrfs_end_transaction(trans); + } + } + } + /* Don't race with allocators so take the groups_sem */ down_write(&space_info->groups_sem); -- 2.53.0