You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Nice feature and the failover test is a good addition. Two correctness concerns worth a look before merge, plus one process nit.
1. The graceful udb.shutdown() call can be cancelled before it runs (force-abort path)
engine/packages/service-manager/src/lib.rs:
if abort {
tokio::time::sleep(Duration::from_millis(50)).await;
rivet_runtime::shutdown().await;// notifies the process-wide SHUTDOWN signalbreak;}
...
// (after the loop)
pools.udb()?.shutdown().await;
rivet_runtime::TermSignal::stop();
start() runs entirely inside the future passed to rivet_runtime::run(), which races it against SHUTDOWN.notified():
tokio::select! {
_ = notify.notified() => { ... None}
res = f => Some(res),}
tokio::select! drops the other branch as soon as one resolves. In the abort path, rivet_runtime::shutdown() fires the notify before the new pools.udb()?.shutdown().await call runs. Once that notification is observed on the next poll of the outer select!, f (the whole start() call tree, including the pending Postgres queries inside pools.udb()?.shutdown().await) is liable to be dropped mid-flight — so the graceful leader handoff this PR adds is exactly the thing most likely to get skipped on a hard/triple-signal abort, silently falling back to TTL-based failover instead.
This doesn't affect the normal single-SIGTERM graceful path (rivet_runtime::shutdown() is never called there before the udb shutdown runs), only the 3x-signal abort branch — but that branch was clearly written with graceful intent (// Give time for services to handle final abort + a 50ms sleep), so it's worth fixing. Simplest fix: move pools.udb()?.shutdown().await (and TermSignal::stop()) to run beforerivet_runtime::shutdown() in the abort branch, or restructure so rivet_runtime::shutdown() is only called once as the very last step.
2. resolver_handle.abort() doesn't guarantee the in-flight renew has actually stopped
fnshutdown<'a>(&'aself) -> BoxFut<'a,()>{Box::pin(asyncmove{// Stop renewing the lease before releasing it so a racing renew cannot re-extend it.self.resolver_handle.abort();self.gc_handle.abort();
resolver::handoff(&self.shared).await;})}
resolver::handoff's doc comment states "Renewal must already be stopped before calling this, otherwise a racing renew could re-extend the lease" — but JoinHandle::abort() is a fire-and-forget cancellation request, not a synchronous stop. If the resolver task's lease::renew(...) UPDATE was already written to the socket (bytes in flight) at the moment abort() is called, Postgres can still execute and commit that renew (re-extending expires_at) afterhandoff()'s release() runs on its own connection, since release() doesn't check expires_at or ordering, just leader_addr. That silently undoes the graceful handoff and leaves the standby waiting out the full TTL anyway — the exact failure mode this PR is meant to eliminate.
Since shutdown(&self) can't get &mut access to resolver_handle to .await it (trait methods are all &self, presumably because DatabaseDriverHandle is an Arc<dyn DatabaseDriver>), actually waiting for the abort to land would need a small refactor (e.g. Mutex<Option<JoinHandle<()>>> so shutdown can .take() and await it before calling handoff). This is a narrow, low-probability race (needs a renew request in flight at the exact moment of shutdown, out of a 3s renew interval), but it's a real gap relative to the invariant the code comment claims.
3. PR title violates the repo's own convention
The title [SLOP(claude-opus-4-8-high)] feat(universaldb): graceful postgres leader handoff on shutdown includes a [SLOP(...)] prefix. Per CLAUDE.md: "Never indicate that a change was written by a coding agent: no model name, no agent name, no [SLOP(...)] prefix in the title, body, or PR text." Worth renaming to a plain conventional-commit title before merge.
Minor / non-blocking
PostgresDatabaseDriver::shutdown() duplicates the abort() calls already present in Drop; harmless since abort() is idempotent, but could be factored into a shared helper.
shutdown() only aborts resolver_handle/gc_handle; the PgListener reconnect task and PostgresShared::cache_refresh_task (both raw tokio::spawn, untracked by any handle) keep running until the whole process exits. Fine given the only call site is immediately before process exit, but worth a comment if shutdown() is ever expected to fully quiesce the driver for reuse.
The new test_postgres_graceful_handoff test is a solid, real-infra integration test (matches the repo's no-mocking testing policy) and correctly exercises Database::shutdown() directly, but doesn't exercise the service-manager SIGTERM/abort wiring, so it wouldn't have caught issue [SVC-2555] Set up issue templates #1 above.
Everything else (lease fencing via leader_addr, the ELECTION_CHANNEL NOTIFY wakeup as a latency optimization over the ELECTION_RETRY poll, re-subscribing on RecvError::Closed) looks correct and well reasoned.
NathanFlurry
changed the title
[SLOP(claude-opus-4-8-high)] feat(universaldb): graceful postgres leader handoff on shutdown
feat(universaldb): graceful postgres leader handoff on shutdown
Jun 26, 2026
MasterPtato
changed the title
feat(universaldb): graceful postgres leader handoff on shutdown
[SLOP(claude-opus-4-8-high)] feat(universaldb): graceful postgres leader handoff on shutdown
Jun 29, 2026
MasterPtato
changed the base branch from
stack/slopfix-test-universaldb-postgres-leader-failover-abort-resolver-task-on-driver-drop-tvmntvqo
to
mainAugust 7, 2026 00:39
MasterPtato
changed the base branch from
main
to
stack/slopfix-test-universaldb-postgres-leader-failover-abort-resolver-task-on-driver-drop-tvmntvqoAugust 7, 2026 01:27
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
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.
No description provided.