Delete the snapshot tree atomically instead of 252 calls
delete_snapshot_tree removed the parent and every child snapshot individually.
On a real pool `zfs snapshot -r` creates one snapshot per descendant dataset --
252 on Tap -- so cleanup was 252 sequential middleware calls.
Slow, but the real problem is that it is not atomic: a job killed part-way
through the sweep leaves behind exactly the orphaned snapshots this function
exists to prevent.
zfs.snapshot.delete accepts {"recursive": True}, which destroys the parent and
all children in one call. Use that as the fast path and keep the name-by-name
sweep as the fallback -- it is still needed when the parent is already gone
(stock's finally can win the race once our mounts are released), which makes a
recursive delete fail while the children survive.
The test fake now emulates real `zfs destroy -r` semantics, so a test cannot pass
while the shipped code deletes only the parent.
76 tests, ruff and shellcheck clean.
This commit is contained in:
@@ -165,6 +165,17 @@
|
||||
middlewared; there is now a regression test that executes apply.sh's own probe
|
||||
code against the real wrapped source.
|
||||
|
||||
### Changed (production audit)
|
||||
|
||||
- **`delete_snapshot_tree` now uses a single recursive delete.** It previously
|
||||
removed the parent and each child snapshot one at a time — 252 sequential
|
||||
middleware calls on a real pool. That is slow, but the real problem is that it
|
||||
is **not atomic**: a run killed part-way through the sweep leaves exactly the
|
||||
orphaned snapshots the function exists to prevent. It now issues one
|
||||
`zfs.snapshot.delete(..., {"recursive": True})` and falls back to the
|
||||
name-by-name sweep only when that fails (e.g. stock's `finally` already removed
|
||||
the parent, which leaves the children behind).
|
||||
|
||||
### Refactored
|
||||
|
||||
- Staging teardown had been copy-pasted into `uninstall.sh` and `recover.sh` —
|
||||
|
||||
Reference in New Issue
Block a user