@@ -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