fix(cluster): remove marked resources together with marks - #3567
Merged
Conversation
The scheduler removes "current mark" records when the marks become invalid - before a cluster respin and when cleaning up a dead cluster instance. The mark's resource records were left behind on those paths, and with the "current mark" records gone, the mark staleness handling could not see the marks anymore, so the resource records were unreachable by every deletion path. Resources (e.g. pools) locked by a marked group of tests then stayed locked for the rest of the testrun, and tests that wanted them waited out the whole grace period and failed. Remove the marked resource records of the cluster instance together with its "current mark" records, in the new `_rm_marks` helper used by both paths and by `_on_marked_test_stop`. The "respin after mark" records are removed too - the promised respin is either scheduled, or converted to "needs respin" by `_on_marked_test_stop` beforehand, or the instance is dead. When the removed mark belongs to the test that is initiating the respin (a non-initial marked test), the test is re-evaluated as the initial test of the mark, so its resources get resolved and created anew. It would otherwise keep the cluster instance for its group while holding no resource records at all, and e.g. a singleton test could lock the whole instance while the marked group is still mid-group. A "needs respin" record is created in that case, so the respin stays scheduled even when the re-evaluation gets delayed e.g. by a resource conflict. The `db_dir` fixture moved to `framework_tests/conftest.py` so it can be shared by the new `test_cluster_getter.py`, which covers `_rm_marks` and all its call site paths.
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a status-db cleanup gap in the cluster scheduler where deleting invalid “current mark” records could leave behind marked resource records (locks/uses) that then became unreachable by all cleanup paths, causing pools/instances to remain blocked for the rest of the test run.
Changes:
- Adds a new
ClusterGetter._rm_markshelper to remove mark-related status records together with marked resource records, and uses it from respin/dead-cluster cleanup paths. - Adjusts marked scheduling to allow a respinning worker to proceed even if its mark is observed on another instance, and forces re-evaluation when a marked test initiates a respin that wipes its own mark/resource records.
- Adds new direct unit tests for
ClusterGetterand centralizes the shareddb_dirfixture inframework_tests/conftest.py.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
framework_tests/test_status_db.py |
Removes the local db_dir fixture (moved to shared conftest). |
framework_tests/test_cluster_getter.py |
Adds focused unit tests covering _rm_marks, respin-init behaviors, and marked-instance selection edge cases. |
framework_tests/conftest.py |
Introduces shared db_dir fixture to reset status DB connection and point it at a temp dir. |
cardano_node_tests/cluster_management/status_db.py |
Updates GC docstring to reflect that marked resource records can also be removed during respin/dead-instance handling. |
cardano_node_tests/cluster_management/cluster_getter.py |
Adds _rm_marks and integrates it into respin/dead-instance cleanup; adjusts marked selection for respinning workers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
`_on_marked_test_stop` consumed the "respin after mark" records and then called `_rm_marks`, which deleted them again - a no-op, but a pointless extra DELETE statement. `_rm_marks` now returns the removed "respin after mark" records and `_on_marked_test_stop` uses the return value for the "needs respin" conversion, so the records are deleted exactly once. This also removes the ordering trap where calling `_rm_marks` before the conversion would silently skip it.
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.
Problem
Follow-up to #3566, last finding from the independent review. The
scheduler removes "current mark" records when marks become invalid -
before a cluster respin (
_init_respin) and when cleaning up a deadcluster instance (
_cleanup_dead_clusters). The mark's resourcerecords were left behind on those paths. With the "current mark"
records gone, the mark staleness handling could not see the marks
anymore, so the leftover resource records were unreachable by every
deletion path: pools locked by a marked group of tests stayed locked
for the rest of the testrun, and tests that wanted them waited out
the whole grace period and failed.
Fix
New
_rm_markshelper removes the marked resource records of thecluster instance together with its "current mark" and "respin after
mark" records; used by both paths above and by
_on_marked_test_stop(which converts "respin after mark" to "needs respin" beforehand).
Unmarked resource records (owned by running tests, cleaned by
on_test_stop) and other instances are untouched. An empty mark isrejected - it would match the unmarked records of running tests.
Scheduler behavior changes
Two behavior changes fell out of the review-fix iterations and are
worth calling out:
A non-initial marked test that initiates a respin is re-evaluated
as the initial test of its mark. Its own mark records - including
the group's resource records - are removed by the respin init, so
the test must resolve its resources anew. Previously it would have
kept the cluster instance for its group while holding no resource
records at all, letting e.g. a singleton test lock the instance
while the marked group is still mid-group. A "needs respin" record
is created at that point, so the respin stays scheduled even when
the re-evaluation is delayed by a resource conflict.
A worker respinning its instance is not blocked by its mark
appearing on another instance. The re-evaluation happens after
the global lock was released, so another worker of the group (e.g.
after the original worker died and xdist re-queued the remaining
tests) can claim the mark elsewhere in that window. The pinned
respinner now proceeds as the first test of the mark instead of
waiting forever. The mark can then exist on two instances - a state
the framework already tolerates; the cost is a duplicated expensive
setup.
Verification
framework_tests/test_cluster_getter.py- first unit tests thatexercise
ClusterGetterdirectly (10 tests):_rm_markssemantics(all-marks/single-mark scoping, other workers' records, unmarked and
other-instance records preserved, empty-mark rejection), both
_init_respinpaths including the own-mark re-evaluation contractand the no-double-wipe short-circuit, the running-tests guard,
_on_marked_test_stopconversion ordering, and the respinnerbypass in
_marked_select_instance. The shareddb_dirfixturemoved to
framework_tests/conftest.py.make lintclean.comment accuracy per round) until convergence; the re-evaluation
path was verified by independent traces of the scheduling loop,
including cross-worker interleavings.