@@ -34,11 +34,6 @@ public enum KadDHT {
3434 public class Node : DHTCore , EventLoopService , LifecycleHandler , PeerRouting , ContentRouting , @unchecked Sendable {
3535 public static let key : String = " KadDHT "
3636
37- enum State : Sendable {
38- case started
39- case stopped
40- }
41-
4237 /// A `TimeAmount` expressed in seconds.
4338 private static func seconds( _ amount: TimeAmount ) -> TimeInterval {
4439 TimeInterval ( amount. nanoseconds) / 1_000_000_000
@@ -148,26 +143,29 @@ public enum KadDHT {
148143 /// Known Peers
149144 let peerstore : PeerStore
150145
151- /// Wether the node should start a timer that triggers the heartbeat method, or if it should wait for an external service to call the heartbeat method explicitly
152- public var autoUpdate : Bool
146+ private var handler : LibP2P . ProtocolHandler ?
153147
154148 var replacementStrategy : RoutingTable . ReplacementStrategy { self . configuration. replacementStrategy }
155149
156- private var heartbeatTask : RepeatedTask ?
157-
158- /// Refresh runs on a slower interval than the maintenance beat, see `start()`.
159- private var refreshTask : RepeatedTask ?
150+ /// If the Node is currently in the process of shutting down, this will contain the shutdowns
151+ /// future, so concurrent / repeated callers can join this future to be notified.
152+ private var pendingShutdown : EventLoopFuture < Void > ?
160153
161154 public private( set) var state : ServiceLifecycleState = . stopped
162155
163- private var handler : LibP2P . ProtocolHandler ?
164-
165156 /// Whether we've registered the route handler already or not, so a restart doesn't
166157 /// try to register `/ipfs/kad/1.0.0` a second time.
167158 private var didRegisterRoute : Bool = false
168159
160+ /// Wether the node should start a timer that triggers the heartbeat method, or if it should wait
161+ /// for an external service to call the heartbeat method explicitly
162+ public var autoUpdate : Bool
163+
164+ private var heartbeatTask : RepeatedTask ?
169165 private var isRunningHeartbeat : Bool = false
170166
167+ /// Refresh runs on a slower interval than the maintenance beat, see `start()`.
168+ private var refreshTask : RepeatedTask ?
171169 private var isRunningRefresh : Bool = false
172170
173171 /// [Namespace: Validator]
@@ -1219,6 +1217,11 @@ public enum KadDHT {
12191217 /// - Returns: A future that always succeeds. A cancellation error is logged rather than propagated
12201218 public func shutdown( ) -> EventLoopFuture < Void > {
12211219 self . eventLoop. flatSubmit {
1220+ /// If there's a shutdown in progress, join it...
1221+ if let pendingShutdown = self . pendingShutdown {
1222+ return pendingShutdown
1223+ }
1224+
12221225 guard self . state == . started || self . state == . starting else {
12231226 self . logger. warning ( " Already stopped " )
12241227 return self . eventLoop. makeSucceededVoidFuture ( )
@@ -1238,13 +1241,21 @@ public enum KadDHT {
12381241 self . heartbeatTask = nil
12391242
12401243 let promise = self . eventLoop. makePromise ( of: Void . self)
1241- heartbeatTask . cancel ( promise : promise )
1242- return promise. futureResult. flatMapErrorThrowing { error in
1244+ // register a post shutdown callback
1245+ let shutdown = promise. futureResult. flatMapErrorThrowing { error in
12431246 self . logger. error ( " Error encountered while stopping node \( error) " )
12441247 } . always { _ in
1248+ // shutdown done, nil out our pendingShutdown
1249+ self . pendingShutdown = nil
1250+ // update our state
12451251 self . state = . stopped
12461252 self . logger. info ( " Node Stopped " )
12471253 }
1254+ // store our shutdown future
1255+ self . pendingShutdown = shutdown
1256+ // cancel the heartbeat task
1257+ heartbeatTask. cancel ( promise: promise)
1258+ return shutdown
12481259 }
12491260 }
12501261
0 commit comments