]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: drain the legacy allocator's writes before arming the write...
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sat, 12 Sep 2026 21:22:55 +0000 (17:22 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 21:40:05 +0000 (17:40 -0400)
Enabling stripe_alloc on a live filesystem (the btrfs.stripe_alloc property)
flips the mount option while the legacy allocator may still have writes in
flight.  Those extents sit in partly used stripes that no stripe run covers,
so the first of them to reach rmw_rbio() after the flip trips
btrfs_stripe_check_write()'s "write to stripe outside any live stripe run"
WARN_ONCE and taints the kernel, although nothing violated the policy: the
data was placed before the policy existed.  punch-hole-warn-repro.sh hits it
on the switch when its killed writers still have dirty data (bhive, 7.3-rc2
lane, 2026-09-12); the same code on 6.18 escaped only by timing.

Keep the check off from the flip until every ordered extent that could have
been allocated before it has completed.  The property is applied inside a
transaction, where flushing delalloc cannot wait, so the drain runs in a
worker: start delalloc on all roots, wait for all ordered extents, clear the
flag.  Extents allocated after the flip have stripe runs and are unaffected;
a mount-time enable (the root directory's property) has nothing in flight and
does not queue the drain.  close_ctree() flushes the worker so it cannot
outlive the filesystem.

Assisted-by: Claude:claude-fable-5-1
fs/btrfs/block-group.c
fs/btrfs/block-group.h
fs/btrfs/disk-io.c
fs/btrfs/fs.h

index 03f8c8d35aacf97c10c79f76e8794a3f0c316177..9bc162eec70b284f87764fe6fc1cd0947077a2c1 100644 (file)
@@ -5,6 +5,8 @@
 #include "misc.h"
 #include "ctree.h"
 #include "block-group.h"
+#include "ordered-data.h"
+#include "btrfs_inode.h"
 #include "space-info.h"
 #include "disk-io.h"
 #include "free-space-cache.h"
@@ -2001,8 +2003,27 @@ static void stripe_alloc_sweep_groups(struct btrfs_fs_info *fs_info, bool arm)
  * when the root directory inode loads its properties -- before any user IO
  * -- or at runtime from btrfs_set_prop().
  */
+/*
+ * Drain the writes the legacy allocator placed before stripe_alloc was
+ * enabled on a live filesystem, then arm the write-hole invariant check.
+ * Runs in a worker: the property that enables the policy is applied inside
+ * a transaction, where flushing delalloc cannot wait.
+ */
+void btrfs_stripe_alloc_enable_work(struct work_struct *work)
+{
+       struct btrfs_fs_info *fs_info = container_of(work, struct btrfs_fs_info,
+                                                    stripe_alloc_enable_work);
+
+       btrfs_start_delalloc_roots(fs_info, LONG_MAX, false);
+       btrfs_wait_ordered_roots(fs_info, U64_MAX, NULL);
+       clear_bit(BTRFS_FS_STRIPE_ALLOC_ENABLING, &fs_info->flags);
+       btrfs_info(fs_info,
+          "stripe_alloc: writes placed before the policy was enabled have landed");
+}
+
 int btrfs_enable_stripe_alloc(struct btrfs_fs_info *fs_info)
 {
+       bool live = test_bit(BTRFS_FS_OPEN, &fs_info->flags);
        int ret;
 
        if (btrfs_test_opt(fs_info, STRIPE_ALLOC))
@@ -2010,6 +2031,17 @@ int btrfs_enable_stripe_alloc(struct btrfs_fs_info *fs_info)
        ret = btrfs_stripe_alloc_check_support(fs_info);
        if (ret)
                return ret;
+       /*
+        * On a live filesystem the legacy allocator may still have writes in
+        * flight.  Their extents sit in partly used stripes that no stripe run
+        * covers, so the write-hole invariant check would report them although
+        * they were placed before the policy existed.  Keep the check off until
+        * every ordered extent that could have been allocated before the flip
+        * has landed; extents allocated after it have stripe runs.  A mount-time
+        * enable has nothing in flight and skips the drain.
+        */
+       if (live)
+               set_bit(BTRFS_FS_STRIPE_ALLOC_ENABLING, &fs_info->flags);
        btrfs_set_opt(fs_info->mount_opt, STRIPE_ALLOC);
        /*
         * Arm stripe_unusable accounting for raid56 data groups whose caches
@@ -2021,6 +2053,8 @@ int btrfs_enable_stripe_alloc(struct btrfs_fs_info *fs_info)
        stripe_alloc_sweep_groups(fs_info, true);
        btrfs_info(fs_info,
                   "using stripe-exclusive allocation for raid56 data");
+       if (live)
+               queue_work(system_unbound_wq, &fs_info->stripe_alloc_enable_work);
        return 0;
 }
 
@@ -2669,6 +2703,9 @@ void btrfs_stripe_check_write(struct btrfs_fs_info *fs_info,
 
        if (!btrfs_test_opt(fs_info, STRIPE_ALLOC))
                return;
+       /* the legacy allocator's writes are still landing after a runtime enable */
+       if (test_bit(BTRFS_FS_STRIPE_ALLOC_ENABLING, &fs_info->flags))
+               return;
        bg = btrfs_lookup_block_group(fs_info, full_stripe_start);
        if (!bg)
                return;
index dcd418d81c3557a415faa97807ea957188ee8441..98d1e8c58c661dd9805a82277c639ae3781cdddd 100644 (file)
@@ -514,6 +514,7 @@ bool btrfs_stripe_alloc_forces_cow(struct btrfs_fs_info *fs_info, u64 bytenr);
 bool btrfs_is_stripe_alloc_bg(const struct btrfs_block_group *bg);
 bool btrfs_is_stripe_meta_bg(const struct btrfs_block_group *bg);
 void btrfs_scan_stripe_unusable(struct btrfs_fs_info *fs_info, bool force);
+void btrfs_stripe_alloc_enable_work(struct work_struct *work);
 #ifdef CONFIG_BTRFS_DEBUG
 void btrfs_stripe_check_write(struct btrfs_fs_info *fs_info,
                              u64 full_stripe_start, bool sub_stripe);
index 2bc975f576e837f127a9bac78be75efa0ec65054..7c3d0b455125123dbfa4e45d9faa873c27f12cd1 100644 (file)
@@ -2751,6 +2751,7 @@ void btrfs_init_fs_info(struct btrfs_fs_info *fs_info)
        INIT_LIST_HEAD(&fs_info->delalloc_roots);
        INIT_LIST_HEAD(&fs_info->caching_block_groups);
        INIT_LIST_HEAD(&fs_info->open_stripe_bgs);
+       INIT_WORK(&fs_info->stripe_alloc_enable_work, btrfs_stripe_alloc_enable_work);
        fs_info->stripe_park_timeout_ms = BTRFS_RBIO_PARK_TIMEOUT_MS;
        fs_info->stripe_park_sync_timeout_ms = BTRFS_RBIO_PARK_SYNC_TIMEOUT_MS;
        spin_lock_init(&fs_info->open_stripe_lock);
@@ -4354,6 +4355,7 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info)
         */
        btrfs_run_delayed_iputs(fs_info);
 
+       flush_work(&fs_info->stripe_alloc_enable_work);
        cancel_work_sync(&fs_info->async_reclaim_work);
        cancel_work_sync(&fs_info->async_data_reclaim_work);
        cancel_work_sync(&fs_info->preempt_reclaim_work);
index 985dc2c6b3e2ad3e300ff892121ea2aab3e7b846..b10c0321d63d73ec437a315f730bbb4ab498919d 100644 (file)
@@ -205,6 +205,13 @@ enum {
         */
        BTRFS_FS_UNALIGNED_TREE_BLOCK,
 
+       /*
+        * stripe_alloc was enabled on a live filesystem and the legacy
+        * allocator's writes have not all landed yet: the write-hole
+        * invariant check stays off until they have.
+        */
+       BTRFS_FS_STRIPE_ALLOC_ENABLING,
+
 #if BITS_PER_LONG == 32
        /* Indicate if we have error/warn message printed on 32bit systems */
        BTRFS_FS_32BIT_ERROR,
@@ -915,6 +922,8 @@ struct btrfs_fs_info {
        struct semaphore uuid_tree_rescan_sem;
 
        /* Used to reclaim the metadata space in the background. */
+       /* drains the legacy allocator's writes after a runtime stripe_alloc enable */
+       struct work_struct stripe_alloc_enable_work;
        struct work_struct async_reclaim_work;
        struct work_struct async_data_reclaim_work;
        struct work_struct preempt_reclaim_work;