]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: give a queued stripe group a chunk to relocate into when the...
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Mon, 7 Sep 2026 11:33:59 +0000 (07:33 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 21:40:05 +0000 (17:40 -0400)
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

index 50a19d3fc9377a6d77c4eae146c1c8408d961fd4..fe987c3542b378485672a03ff6112af9e4809ca3 100644 (file)
@@ -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);