btrfs: resumed snapshot deletion resets drop_progress to zero

Kernel fix, fstests regression test, reproducers and test logs.

The kernel patch was posted to linux-btrfs@vger.kernel.org (thread). The fstests test (btrfs/355) was posted separately to fstests@vger.kernel.org (thread). Neither is merged.

The bug

Deleting a subvolume is done in the background by btrfs_drop_snapshot(), which saves its position (drop_progress, drop_level) in the root item so it can resume after an unmount.

Introduced by aea6f028d01d ("btrfs: save drop_progress if we drop refs at all", v5.1-rc1). Still present in v7.4-rc1.

The fix

Seed wc->drop_progress and wc->drop_level from the root item on resume, so a checkpoint before the first drop rewrites the same resume point:

 		btrfs_disk_key_to_cpu(&key, &root_item->drop_progress);
 		memcpy(&wc->update_progress, &key,
 		       sizeof(wc->update_progress));
+		/* keep the resume point until this run drops something itself */
+		memcpy(&wc->drop_progress, &key, sizeof(wc->drop_progress));

 		level = btrfs_root_drop_level(root_item);
 		BUG_ON(level == 0);
+		wc->drop_level = level;

Files

0001-btrfs-keep-drop_progress-when-resuming-snapshot-deletion.patch
Kernel fix, git format-patch against v7.3.1. Applies to v7.0.14 through v7.4-rc1.
0001-btrfs-test-that-a-resumed-snapshot-deletion-keeps-it.patch
fstests regression test btrfs/355, against fstests master. Plain files: fstests/355, fstests/355.out.
repro-simple2.sh
Minimal reproducer, ~70 s. Prints BUG: ... and exits 1 on the abort. Needs mkfiles.py.
repro-simple.sh
Same sequence with checks after each step: drop key, dmesg, btrfs check --readonly, snapshot content. Exit 0 bug, 1 correct, 2 inconclusive. Needs mkfiles.py and manifest.py.
SUMMARY.md
Write-up: results, environment, reproducers, limits.
logs/
Per-run logs, listed in logs/index.html: ab/ (reproducers), btrfs-355/, fstests-quick/, dmesg-abort-lines.txt.
README.txt, sha256sums
Plain-text version of this page; checksums (sha256sum -c sha256sums).

Reproducing

Use a throwaway VM. The scripts only touch an image file they create, but the bug aborts a transaction in the running kernel.

sudo ./repro-simple2.sh /var/tmp/work     # btrfs-progs, dmsetup, python3
sudo ./repro-simple.sh  /var/tmp/work

Sequence: 2 GB loop image with dm-delay, mkfs.btrfs -n 4096, subvolume S1 with 100k files (level 3 tree), read-only snapshot S2, delete S1, unmount after the first root child is dropped, mount and unmount right away, then let the drop finish.

fstests: copy fstests/355* to tests/btrfs/ (or apply the patch), then:

./check btrfs/355

btrfs/355 needs no dm-delay: 30k files, checks the drop key after each of five quick resumes, then S2's content and that S1 is gone. About 15 s.

Results

Ubuntu 26.10 VM, kernel 7.3.0-9-generic (v7.3-rc6 based). Self-built modules come from the matching Ubuntu source with the same toolchain as stock, and differ only by the patch.

repro-simple.sh, 3 runs per module

ModuleDrop key zeroedAbortbtrfs checkSnapshot S2
stock3/33/3errors 3/3EIO 3/3
self-built, unpatched3/33/3errors 3/3EIO 3/3
self-built, patched0/30/3clean 3/3intact 3/3

repro-simple2.sh: abort 3/3 on stock and unpatched, 0/3 patched. With the patch the drop key kept the resume point over 20 mount/unmount cycles and the next mount finished the drop.

fstests btrfs/355 (final version)

StorageStockUnpatchedPatchedTime
SATA SSD, loop files on ext43/3 fail3/3 fail3/3 pass15–17 s
loop files on tmpfs–3/3 fail3/3 pass11–13 s

Each failing run: drop key 0 after the first resume, abort in dmesg, S2 content changed, btrfs check errors.

fstests -g quick: 765 of 766 executed tests pass with and without the patch. generic/574 (fs-verity, 1K blocks) fails in both, unrelated.

Limits