]> 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:49 +0000 (17:22 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 18 Sep 2026 21:36:25 +0000 (17:36 -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 bb34b93b5fe3ea7820c1b8b0bee37b52b2ffef32..98563a27482127795757c598b01b9c805be7dadf 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"
@@ -2034,8 +2036,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))
@@ -2043,6 +2064,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
@@ -2054,6 +2086,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;
 }
 
@@ -2702,6 +2736,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 bacabac29e35169c4f4cef2622750af7da2e88b9..f8cb733ad377286e09f1eb0c911206d539e62e5e 100644 (file)
@@ -524,6 +524,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 40e972b1c349b3a8d1454cb13cc6ad45973cd513..42bb88b32c2db4e52301c197c61e1115477edd6d 100644 (file)
@@ -2797,6 +2797,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);
@@ -4496,6 +4497,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 ba99bef6de55ca952aa8bd93b507368ed7851757..04a36349e6cd513640537a3f7a6c3d68e6584575 100644 (file)
@@ -234,6 +234,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,
@@ -962,6 +969,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;