Skip to content

Commit 89f576e

Browse files
authored
Fix Shutdown Race (#26)
* removed unused State enum * updated shutdown logic to include a pendingShutdown future that subsequent callers can join so slow shutdowns are handled correctly.
1 parent 73807ab commit 89f576e

1 file changed

Lines changed: 26 additions & 15 deletions

File tree

Sources/LibP2PKadDHT/Node.swift

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)