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

From: FAN YE via B4 Relay

Date: Sun Aug 23 2026 - 05:28:38 EST


From: FAN YE <fy15309206903@xxxxxxxxx>

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 <fy15309206903@xxxxxxxxx>
---
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 <fy15309206903@xxxxxxxxx>