]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: never let relocation wait on the whole-stripe gate
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sat, 5 Sep 2026 16:16:00 +0000 (12:16 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 21:40:05 +0000 (17:40 -0400)
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
fs/btrfs/space-info.c
fs/btrfs/space-info.h

index e8a08de2421e995b4c7a5b4a9bfe4eea462bdf7b..c36e1d531323e6bc567f8fb260a928596b43b65b 100644 (file)
@@ -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);
 
index b358162180441eb0c7969aabea00622911ccb2f8..e74cbab0f5715bc0ecf1582b4464dd07dd965844 100644 (file)
@@ -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) {
index ae85c4208b81d94c3d1fffefb726a3bd58b75e2d..dbf349a04fe79e215b0ef64707dc3253566c60f0 100644 (file)
@@ -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,