Skip to content

topology: separate publication from lifecycle notifications - #6

Closed
dkropachev wants to merge 7 commits into
dk/lifecycle-coordination-basefrom
dk/lifecycle-coordination
Closed

dkropachev wants to merge 7 commits into
dk/lifecycle-coordination-basefrom
dk/lifecycle-coordination

Conversation

@dkropachev

Copy link
Copy Markdown
Owner

Summary

Alternative to scylladb#1036 for scylladb#1007. Missing-host reconciliation must retire hosts and clean up pools, policies, listeners, and reconnectors. Serializing the whole refresh across callbacks can deadlock when a listener calls synchronous refresh while another thread waits for that listener's host lock.

Publish topology changes in short transactions, then drain ordered lifecycle operations without topology or host callback locks. Check exact Host identity before stale queued work can take effect, reserve new hosts until their addition initializes, and discard refresh snapshots overtaken by a newer accepted refresh. Removal notifications precede replacement addition notifications; callback failures do not strand the queue.

This draft explores option 3 from the scylladb#1036 design discussion: separate publication and notification coordination instead of extending topology-first locking across lifecycle callbacks. The upstream PR remains unchanged.

Behavior and compatibility

  • refresh_nodes() synchronously publishes node-list and token metadata. If a lifecycle notification is already running, it can return before queued notifications finish. Pool availability can lag publication.
  • An active callback can finish after its Host is retired. Stale queued UP, DOWN, and ADD work is discarded. Nested refresh appends notifications instead of delivering them recursively.
  • DC/rack changes are queued with removal from the old policy location and addition at the new location. They can lag node-list publication. Newer queued location changes supersede older desired locations, and location publication replaces the token-map cache owner.
  • Notifications share an ordered dispatcher. A slow listener delays later notifications, but does not hold the topology publication lock. Listeners must not wait for a later notification from the same cluster.
  • Refresh ordering is client-side; it does not establish a server-side topology version. No wire-protocol change is intended.

Decisions

  • Choice: Separate topology publication from ordered lifecycle notification delivery. Run notifications without topology or host callback locks so listeners can refresh synchronously without lock inversion. Source: user selected option 3 as a separate draft alternative to cluster: coordinate topology publication and host lifecycle scylladb/python-driver#1036.
  • Choice: Synchronous refresh guarantees node-list and token publication, not completion of queued lifecycle notifications or DC/rack policy transitions. An active callback may finish after retirement; subsequent stale work is discarded. This permits callback re-entry while preserving terminal Host identity and notification order.

Refs scylladb#1007, scylladb#921, scylladb#1036.

Branch layout

Targets dk/lifecycle-coordination-base, a snapshot of upstream master at e152e3a11631f07b5ba0bb29b0f8c11e41b446ab. Fork master is behind upstream; the dedicated base keeps this diff limited to the alternative without updating fork master.

Validation

  • Full unit run: 1,587 passed, 134 skipped, 931 subtests passed. The final location-event adjustment was followed by the focused suite: 227 passed.
  • Cython translation of cluster.py and git diff --check passed.
  • Independent correctness findings were fixed; focused verification of the fixes found no remaining findings.
  • Live-cluster integration and remote CI have not been run for this fork draft. Required integration scenarios include node removal/rebirth, endpoint movement, and listener-triggered refresh during lifecycle changes.

Pre-review checklist

  • I have split my patch into logically separate commits.
  • All commit messages clearly explain what they change and why.
  • I added relevant tests for new features and bug fixes.
  • All commits compile, pass static checks and pass test.
  • PR description sums up the changes and reasons why they should be introduced.
  • I have provided docstrings for the public items that I want to introduce. Existing listener and refresh contracts are documented.
  • I have adjusted the documentation in ./docs/source/. This repository uses docs/api/.
  • I added appropriate Fixes: annotations to PR description. This fork draft references upstream issues without closing them.

Topology cleanup must retire missing hosts without retaining callback
locks across policies or listeners. Otherwise a listener's synchronous
refresh can deadlock against removal or let old cleanup overtake a newer
Host with the same identity.

Publish terminal state and queue ordered lifecycle work separately. Drain
notifications without the coordination mutex and without recursive
delivery. Reserve additions until ready, fence recovery by generation,
and discard refresh snapshots overtaken by a newer accepted request.

Keep DC/rack policy transitions ordered with their location changes and
replace replica caches afterward. Document that refresh publishes the
node list and token map synchronously, while notifications and location
transitions can remain queued behind a running callback.

Cover reentrant refresh, concurrent retirement, stale snapshots, custom
policy callbacks, recovery reservations, location reversals and failures.

Refs: scylladb#1007
Refs: scylladb#1036
Keep event admission, addition readiness, retirement and replacement
ordering in the lifecycle coordinator so callers do not assemble partial
transitions. Move queued DC/rack updates out of the control connection.
Metadata still owns indexes and token maps; cluster and session code keep
the policy, pool and connection operations.

Preserve generation-based DOWN admission during metadata reindexing and
exact-host checks for ADD and UP. Notification submission now consistently
returns no result, whether delivery starts immediately or is deferred.

Cover deferred admission, retirement during readiness reset and the
notification return contract.
Close lifecycle admission before tearing down pools and executors. Drop
queued notifications, clear pending host reservations, and cancel current
or retired reconnectors without joining an active callback. A listener
can shut down its cluster from an executor worker without joining itself.

Keep UP and DOWN admission, completion flags and generation changes in
the lifecycle coordinator. Make retirement idempotent and prevent late
refresh results or pool completions from reviving closed lifecycle work.

Cover callback-triggered shutdown, concurrent queue closure, late pool
completion, repeated retirement and refresh results arriving after close.
Release ordinary failed ADD reservations so a healthy sibling pool cannot
leave a session permanently unable to reconcile. Stop ADD preparation if
its distance policy shuts down the cluster.

Carry forced token rebuild intent into a newer accepted refresh while
still discarding older topology data. Bind a stale candidate socket to
its current observed host without republishing its snapshot, and reject a
candidate whose route has since moved.

Exercise these races and assert callback outcomes outside exception
isolation. Clarify that executor teardown can still wait for workers even
though closing lifecycle admission does not join callbacks.
Pass pool-failure ownership through policy callbacks as a reservation and
recheck it atomically with DOWN admission. Releasing session and host
locks before invoking policies avoids inversion with topology publication
without letting a superseded failure mark the winning pool down.

Retain all unfulfilled forced-refresh requests until a sufficiently new
snapshot rebuilds tokens. Finish terminal retirement when shutdown races
metadata replacement and cancel detached reconnectors during cleanup.

Strengthen shutdown and UP/DOWN ownership tests with assertions outside
callback exception handling and deterministic supersession scenarios.
Queue DOWN in the same publication transaction that claims its state.
Deferring queue insertion until an executor worker starts lets a later UP
overtake DOWN and leaves policy state out of sync with host health.

Let the executor drain already-admitted lifecycle work while keeping the
caller nonblocking. Remove unclaimed events if executor submission fails,
and preserve acceptance if another drainer already claimed the event.

Cover delayed executor delivery and rejected handoff cleanup.
Move lifecycle coordination, pool-failure reservations and their helpers
into cassandra._lifecycle. Use ordinary class names within that private
module and keep them out of the cluster module's public namespace.

Declare no wildcard exports, avoid importing Cluster back into the
lifecycle module, and preserve the existing cluster logging category.
Update internal callers and tests without changing lifecycle behavior.
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.

1 participant