Skip to content

fix(cluster): remove marked resources together with marks - #3567

Merged
mkoura merged 2 commits into
masterfrom
marked_resources_cleanup
Jul 29, 2026
Merged

fix(cluster): remove marked resources together with marks#3567
mkoura merged 2 commits into
masterfrom
marked_resources_cleanup

Conversation

@mkoura

@mkoura mkoura commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

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 dead
cluster instance (_cleanup_dead_clusters). The mark's resource
records 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_marks helper removes the marked resource records of the
cluster 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 is
rejected - 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

  • New framework_tests/test_cluster_getter.py - first unit tests that
    exercise ClusterGetter directly (10 tests): _rm_marks semantics
    (all-marks/single-mark scoping, other workers' records, unmarked and
    other-instance records preserved, empty-mark rejection), both
    _init_respin paths including the own-mark re-evaluation contract
    and the no-double-wipe short-circuit, the running-tests guard,
    _on_marked_test_stop conversion ordering, and the respinner
    bypass in _marked_select_instance. The shared db_dir fixture
    moved to framework_tests/conftest.py.
  • All 50 framework tests pass, make lint clean.
  • Reviewed in a 4-round review-fix loop (code review, test coverage,
    comment accuracy per round) until convergence; the re-evaluation
    path was verified by independent traces of the scheduling loop,
    including cross-worker interleavings.

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.
@mkoura
mkoura requested a review from saratomaz as a code owner July 29, 2026 15:23
@mkoura
mkoura requested review from Copilot and removed request for saratomaz July 29, 2026 15:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_marks helper 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 ClusterGetter and centralizes the shared db_dir fixture in framework_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.

Comment thread cardano_node_tests/cluster_management/cluster_getter.py Outdated
`_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.
@mkoura
mkoura merged commit a79cd73 into master Jul 29, 2026
3 checks passed
@mkoura
mkoura deleted the marked_resources_cleanup branch July 29, 2026 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants