snapshot: fix delete race producing stuck open_ref / empty clone entries#1018
Open
schmidt-scaled wants to merge 1 commit into
Open
snapshot: fix delete race producing stuck open_ref / empty clone entries#1018schmidt-scaled wants to merge 1 commit into
schmidt-scaled wants to merge 1 commit into
Conversation
Three independent fixes that together close the "Cannot remove snapshot because it is open" / EBUSY (-16) state where the snapshot ends up with non-zero open_ref but no clone entries and can only be cleared by restarting the host node. 1. Bump random VUID space from 10k to 1M and dedupe against existing CLN_/LVOL_/SNAP_ bdev-name numeric suffixes. With ~10k lvols+snaps the legacy 10k range hit ~50% birthday-collision probability, producing repeated SPDK "lvol with name already exists" rejections that triggered the async-delete-then-reuse sequence below. 2. snapshot_controller.add and .clone reject ops on a target that is in pending deletion (lvol STATUS_IN_DELETION; snapshot STATUS_IN_DELETION or deleted=True). Closes the window between an async delete being issued and a fresh create slipping through against the same blob, which left snapshot parent metadata partially overwritten by the new clone's lineage. 3. snapshot_controller.delete blocks the snapshot's hard-delete while any clone's SPDK-side delete is still in flight. Previously any IN_DELETION clone was treated as "already gone" and the snap delete proceeded to call SPDK, which returned EBUSY because the clone's bdev was still open. Now a clone counts as gone only when its deletion_status field has been set (i.e. the leader's delete_lvol_from_node returned). Otherwise the snapshot is soft-deleted; the clone's own delete-completion path will re-trigger the hard delete once SPDK has actually released it. Tests: tests/test_snapshot_delete_race.py covers all three fixes (10 tests, all green). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hamdy-khader
reviewed
May 1, 2026
| _BDEV_NAME_NUMERIC_SUFFIX = re.compile(r'(?:^|[/_])(\d+)\s*$') | ||
|
|
||
|
|
||
| def _used_bdev_name_numbers(db_controller): |
Collaborator
There was a problem hiding this comment.
This function can be written like this:
def _used_bdev_name_numbers(db_controller):
used = set()
for lvol in db_controller.get_lvols():
used.add(lvol.vuid)
for snap in db_controller.get_snapshots():
used.add(snap.vuid)
return used
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three independent fixes that together close the "Cannot remove snapshot because it is open" / EBUSY (-16) state where the snapshot ends up with non-zero
open_refbut no clone entries and can only be cleared by restarting the host node (incident: aws_dual_soak 2026-04-30, 14 stuck snapshots).Fixes
Random VUID dedupe & range bump (
simplyblock_core/utils/__init__.py)get_random_vuidrange goes from 10k → 1M.get_random_vuidandget_random_snapshot_vuiddedupe against numeric suffixes parsed out of existingCLN_/LVOL_/SNAP_bdev names cluster-wide.lvol with name ... already existsrejection, the mgmt async-delete, and the reuse-during-deletion sequence.Reject snapshot/clone ops on pending-deletion targets (
snapshot_controller.add/snapshot_controller.clone)addrejects when source lvol isSTATUS_IN_DELETION.clonerejects when source snapshot hasdeleted=TrueorSTATUS_IN_DELETION.Snapshot delete waits for clone SPDK delete (
snapshot_controller.delete)deletion_statusfield has been set (i.e. leader'sdelete_lvol_from_nodereturned). AnIN_DELETIONclone with nodeletion_statusis still SPDK-blocking and the snapshot is soft-deleted instead.Test plan
tests/test_snapshot_delete_race.py— 10 unit tests covering all three fixes (random vuid dedupe, add/clone rejection, delete waits for in-flight clone)tests/suite green (running)🤖 Generated with Claude Code