[PATCH] btrfs: scrub: wake up cancel_dev waiters after clearing dev->scrub_ctx

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 <20260823-btrfs-scrub-cancel-dev-wakeup-v1-1-374d618ab25f@gmail.com>
From: FAN YE <[email protected]>

btrfs_scrub_cancel_dev() waits on fs_info->scrub_pause_wait for
dev->scrub_ctx to become NULL.  btrfs_scrub_dev() wakes that queue right
after dropping scrubs_running, several statements before it clears the
pointer, and never wakes it again.  The canceller is already queued by
then: it sets sctx->cancel_req before waiting, and the scrub only starts
finishing once should_cancel_scrub() observes that flag.  So the wakeup it
gets is the early one, its recheck still finds dev->scrub_ctx set, and it
goes back to sleep before the store it is waiting for.

Nothing wakes the queue after the store.  scrubs_running is already zero,
and btrfs_rm_device() reaches btrfs_scrub_cancel_dev() holding a
transaction handle, so the commit that would call btrfs_scrub_continue()
blocks behind the sleeping canceller.  Device removal, the transaction
kthread and any fsync() on the filesystem hang indefinitely.  Wake the
queue after the store as well.

Fixes: a2de733c78fa ("btrfs: scrub")
Assisted-by: Claude:claude-opus-5 sashiko
Signed-off-by: FAN YE <[email protected]>
---
Reproduced on unmodified kernels in a VM: a scrub running on the device
btrfs_rm_device() removes.  Upstream hangs in all 7 attempts where the
canceller actually slept - 4 with transaction commits forced back to back,
3 at the default commit interval - and 0 of 16 with this patch.  ftrace
records no sched_wakeup at all for the blocked task after dev->scrub_ctx is
seen NULL, while btrfs-transacti and a plain BTRFS_IOC_SYNC block behind
it.  Attempts where the cancel returned -ENOTCONN, or returned 0 without
ever sleeping, are not counted either way.  Moving the existing wakeup
after the store instead of adding one also works (0 of 9) and is not
measurably cheaper; this keeps each wakeup next to the store it publishes.
Compile-tested (W=1, x86_64 defconfig + CONFIG_BTRFS_FS=y).
---
 fs/btrfs/scrub.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c
index f209e75f0ff5..43fee426fdab 100644
--- a/fs/btrfs/scrub.c
+++ b/fs/btrfs/scrub.c
@@ -3177,6 +3177,7 @@ int btrfs_scrub_dev(struct btrfs_fs_info *fs_info, u64 devid, u64 start,
 	mutex_lock(&fs_info->scrub_lock);
 	dev->scrub_ctx = NULL;
 	mutex_unlock(&fs_info->scrub_lock);
+	wake_up(&fs_info->scrub_pause_wait);
 
 	scrub_workers_put(fs_info);
 	scrub_put_ctx(sctx);

---
base-commit: 2709dd5ae32f0828f386327c76bba9f39f63a1c6
change-id: 20260823-btrfs-scrub-cancel-dev-wakeup-fb2e93267f50

Best regards,
--  
FAN YE <[email protected]>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.