Skip to content

Commit 55f4f33

Browse files
sanityclaude
andauthored
fix(ring): plumb shutdown CancellationToken into spawned background loops (#4544)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a27e333 commit 55f4f33

5 files changed

Lines changed: 350 additions & 36 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ reqwest = { version = "0.12", default-features = false, features = ["json", "str
4242
tokio = { version = "1", features = ["full"] }
4343
tokio-stream = "0.1.18"
4444
tokio-tungstenite = "0.27.0"
45+
tokio-util = "0.7"
4546
tower-http = { version = "0.7", features = ["fs", "trace"] }
4647

4748
# Crypto

crates/core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ tempfile = { workspace = true }
8888
thiserror = { workspace = true }
8989
tokio = { workspace = true, features = ["fs", "macros", "rt-multi-thread", "signal", "sync", "process", "tracing", "time", "test-util"] }
9090
tokio-tungstenite = { workspace = true }
91+
tokio-util = { workspace = true, features = ["rt"] }
9192
tower-http = { workspace = true }
9293
ulid = { workspace = true }
9394
zeroize = { workspace = true }

crates/core/src/node/p2p_impl.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,14 @@ pub(crate) struct NodeP2P {
7070
/// and the redb file lock held for the process lifetime (issue #4401).
7171
///
7272
/// On drop it:
73-
/// 1. aborts the stored detached task handles (executor + client-events, plus
73+
/// 1. triggers the ring's background-task shutdown token so the long-lived
74+
/// `Ring::new` loops (governance reaper, interest heartbeat, connection
75+
/// maintenance, telemetry) stop promptly instead of waiting out their
76+
/// longest sleep (issue #4278),
77+
/// 2. aborts the stored detached task handles (executor + client-events, plus
7478
/// the session-actor / result-router / initial-join / aggressive-connect
7579
/// tasks once they exist), and
76-
/// 2. clears the ring's redb `Storage` clones via `clear_redb_storage()`.
80+
/// 3. clears the ring's redb `Storage` clones via `clear_redb_storage()`.
7781
///
7882
/// ## Exhaustive redb `Arc<Database>` holder set (verified for #4401)
7983
///
@@ -120,6 +124,16 @@ impl Drop for ShutdownTeardown {
120124
return;
121125
}
122126
self.fired = true;
127+
// Signal the Ring's long-lived background tasks (governance reaper,
128+
// interest heartbeat, connection maintenance, telemetry loops, etc.)
129+
// to stop. They race this token against their sleeps via
130+
// `tokio::select!`, so a graceful shutdown returns promptly instead of
131+
// waiting up to ~5 minutes for the longest outstanding sleep to elapse.
132+
// Unlike the abort handles below, these tasks are only *observed* by
133+
// the BackgroundTaskMonitor (their JoinHandles are consumed there, not
134+
// aborted), so the cancellation token is the mechanism that actually
135+
// stops them. See issue #4278.
136+
self.ring.trigger_shutdown();
123137
for abort in &self.aborts {
124138
abort.abort();
125139
}

0 commit comments

Comments
 (0)