From fc42dafa6085a50b96a4f2755d91c1d477c64ba4 Mon Sep 17 00:00:00 2001 From: Zygo Blaxell Date: Sat, 12 Sep 2026 17:22:55 -0400 Subject: [PATCH] btrfs: stripe_alloc: drain the legacy allocator's writes before arming the write-hole check at a runtime enable 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 | 37 +++++++++++++++++++++++++++++++++++++ fs/btrfs/block-group.h | 1 + fs/btrfs/disk-io.c | 2 ++ fs/btrfs/fs.h | 9 +++++++++ 4 files changed, 49 insertions(+) diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index 03f8c8d35aacf..9bc162eec70b2 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -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; diff --git a/fs/btrfs/block-group.h b/fs/btrfs/block-group.h index dcd418d81c355..98d1e8c58c661 100644 --- a/fs/btrfs/block-group.h +++ b/fs/btrfs/block-group.h @@ -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); diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 2bc975f576e83..7c3d0b4551251 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -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); diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h index 985dc2c6b3e2a..b10c0321d63d7 100644 --- a/fs/btrfs/fs.h +++ b/fs/btrfs/fs.h @@ -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; -- 2.53.0