[PATCH] btrfs: dev-replace: fix missing barrier before waking bio counter waiters
FAN YE via B4 Relay <[email protected]>
| Newsgroups | org.kernel.vger.linux-btrfs,org.kernel.feeds.b4-sent,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <20260822-btrfs-dev-replace-bio-counter-barrier-v1-1-e718ae7d25e5@gmail.com> |
From: FAN YE <[email protected]> btrfs_bio_counter_sub() gates the wakeup on cond_wake_up_nomb(), whose bare waitqueue_active() requires a full barrier from the preceding code. percpu_counter_sub() does not provide one, its fast path is a plain per-cpu update, so the waitqueue_active() load can be hoisted over the counter update. btrfs_rm_dev_replace_blocked() waits for percpu_counter_sum() to reach zero and can then be left sleeping by the very decrement that reaches it. It stays asleep until some unrelated later decrement wakes it, with BTRFS_FS_STATE_DEV_REPLACING set the whole time, so every bio submitter on the filesystem blocks as well. Use cond_wake_up(), which has the barrier. Fixes: 4245215d6a8d ("Btrfs, raid56: fix use-after-free problem in the final device replace procedure on raid56") Assisted-by: Claude:claude-opus-5 Signed-off-by: FAN YE <[email protected]> --- Two independent checks. A litmus test of the same shape: herd7 with the LKMM allows the interleaving and forbids it with smp_mb(); built with klitmus7 it hits 349326 times in 64000000 iterations on x86_64, 0 times with the barrier. With that interleaving staged in the dev-replace path in a VM (the last in-flight bio released while the finishing waiter is queueing), the wakeup is lost in 3/3 runs and the filesystem stalls for 24.5s until an unrelated decrement arrives, 0/3 with this patch. Gating the barrier on BTRFS_FS_STATE_DEV_REPLACING to keep it off the common path does not work: that test_bit() is unordered as well, and hoisting it over the decrement drops the wakeup in 3/3 runs. Compile-tested (W=1, x86_64 defconfig + CONFIG_BTRFS_FS=y). --- fs/btrfs/dev-replace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/dev-replace.c b/fs/btrfs/dev-replace.c index dc0834f920c3..b2791480e263 100644 --- a/fs/btrfs/dev-replace.c +++ b/fs/btrfs/dev-replace.c @@ -1353,7 +1353,7 @@ bool __pure btrfs_dev_replace_is_ongoing(struct btrfs_dev_replace *dev_replace) void btrfs_bio_counter_sub(struct btrfs_fs_info *fs_info, s64 amount) { percpu_counter_sub(&fs_info->dev_replace.bio_counter, amount); - cond_wake_up_nomb(&fs_info->dev_replace.replace_wait); + cond_wake_up(&fs_info->dev_replace.replace_wait); } void btrfs_bio_counter_inc_blocked(struct btrfs_fs_info *fs_info) --- base-commit: 26260251022fbc2f248a3d747a9b2b961b18d2d8 change-id: 20260822-btrfs-dev-replace-bio-counter-barrier-4a20eb35187a Best regards, -- FAN YE <[email protected]>