From 7ed5cea7f8a0f2a2fd184f2ddfb9b02d85600c09 Mon Sep 17 00:00:00 2001 From: Zygo Blaxell Date: Sat, 5 Sep 2026 12:16:00 -0400 Subject: [PATCH] btrfs: stripe_alloc: never let relocation wait on the whole-stripe gate The data admission gate refuses reservations that would not fit in the whole-stripe supply, and a refused reservation waits as a flush ticket for space to appear. Relocation reserves its prealloc clusters through the same path, so at the fill edge the reclaim worker sat for half an hour in btrfs_reclaim_bgs_work -> btrfs_relocate_chunk -> relocate_block_group -> relocate_file_extent_cluster -> prealloc_file_extent_cluster -> btrfs_alloc_data_chunk_ondemand -> __reserve_bytes holding the exclusive operation and the group it was emptying, while the gate waited for exactly the stripes that relocation was supposed to free. User writers queued behind that head ticket and unmount hung on their writeback. Exempting relocation from the gate altogether is wrong too: it then takes whole stripes that admitted writers were promised, and their writebacks are dropped at the edge. Give relocation its own flush type, BTRFS_RESERVE_FLUSH_DATA_RELOC, that is subject to the same bound but never waits: if the reservation does not fit beyond the bytes already promised it fails at once, the relocation is abandoned cleanly and is retried when the trigger next finds room. Zoned relocation already has a flush type of its own for the same reason. Assisted-by: Claude:claude-fable-5 --- fs/btrfs/delalloc-space.c | 4 ++++ fs/btrfs/space-info.c | 16 +++++++++++++--- fs/btrfs/space-info.h | 8 ++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/delalloc-space.c b/fs/btrfs/delalloc-space.c index e8a08de2421e9..c36e1d531323e 100644 --- a/fs/btrfs/delalloc-space.c +++ b/fs/btrfs/delalloc-space.c @@ -129,6 +129,10 @@ int btrfs_alloc_data_chunk_ondemand(const struct btrfs_inode *inode, u64 bytes) struct btrfs_fs_info *fs_info = root->fs_info; enum btrfs_reserve_flush_enum flush = BTRFS_RESERVE_FLUSH_DATA; + /* Relocation's prealloc clusters must not wait on the stripe_alloc gate. */ + if (btrfs_is_data_reloc_root(root)) + flush = BTRFS_RESERVE_FLUSH_DATA_RELOC; + /* Make sure bytes are sectorsize aligned */ bytes = ALIGN(bytes, fs_info->sectorsize); diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c index b358162180441..e74cbab0f5715 100644 --- a/fs/btrfs/space-info.c +++ b/fs/btrfs/space-info.c @@ -1736,6 +1736,7 @@ static int handle_reserve_ticket(struct btrfs_fs_info *fs_info, switch (flush) { case BTRFS_RESERVE_FLUSH_DATA: + case BTRFS_RESERVE_FLUSH_DATA_RELOC: case BTRFS_RESERVE_FLUSH_ALL: case BTRFS_RESERVE_FLUSH_ALL_STEAL: wait_reserve_ticket(space_info, ticket); @@ -1812,8 +1813,13 @@ static inline bool can_steal(enum btrfs_reserve_flush_enum flush) */ static inline bool can_ticket(enum btrfs_reserve_flush_enum flush) { + /* + * Relocation never waits for space: at the fill edge the stripes it + * would wait for are the ones only it can free (stripe_alloc). + */ return (flush != BTRFS_RESERVE_NO_FLUSH && - flush != BTRFS_RESERVE_FLUSH_EMERGENCY); + flush != BTRFS_RESERVE_FLUSH_EMERGENCY && + flush != BTRFS_RESERVE_FLUSH_DATA_RELOC); } /* @@ -1856,7 +1862,8 @@ static int __reserve_bytes(struct btrfs_fs_info *fs_info, ASSERT(flush != BTRFS_RESERVE_FLUSH_EVICT); } - if (flush == BTRFS_RESERVE_FLUSH_DATA) + if (flush == BTRFS_RESERVE_FLUSH_DATA || + flush == BTRFS_RESERVE_FLUSH_DATA_RELOC) async_work = &fs_info->async_data_reclaim_work; else async_work = &fs_info->async_reclaim_work; @@ -2014,9 +2021,12 @@ int btrfs_reserve_data_bytes(struct btrfs_space_info *space_info, u64 bytes, int ret; ASSERT(flush == BTRFS_RESERVE_FLUSH_DATA || + flush == BTRFS_RESERVE_FLUSH_DATA_RELOC || flush == BTRFS_RESERVE_FLUSH_FREE_SPACE_INODE || flush == BTRFS_RESERVE_NO_FLUSH); - ASSERT(!current->journal_info || flush != BTRFS_RESERVE_FLUSH_DATA); + ASSERT(!current->journal_info || + (flush != BTRFS_RESERVE_FLUSH_DATA && + flush != BTRFS_RESERVE_FLUSH_DATA_RELOC)); ret = __reserve_bytes(fs_info, space_info, bytes, flush); if (ret == -ENOSPC) { diff --git a/fs/btrfs/space-info.h b/fs/btrfs/space-info.h index ae85c4208b81d..dbf349a04fe79 100644 --- a/fs/btrfs/space-info.h +++ b/fs/btrfs/space-info.h @@ -49,6 +49,14 @@ enum btrfs_reserve_flush_enum { * Can be interrupted by a fatal signal. */ BTRFS_RESERVE_FLUSH_DATA, + + /* + * Data reservation for relocation: flushes like FLUSH_DATA but + * never waits on a ticket when the stripe_alloc whole-stripe gate + * refuses it -- relocation is what frees whole stripes, so it must + * not wait for them; it fails at once and is retried later. + */ + BTRFS_RESERVE_FLUSH_DATA_RELOC, BTRFS_RESERVE_FLUSH_FREE_SPACE_INODE, BTRFS_RESERVE_FLUSH_ALL, -- 2.53.0