fix: improve connection stability and add connection check API
- Fix race condition in resource cleanup during disconnect/cancel - Trigger reconnect on ping send failure - Trigger reconnect on write operation failure - Add isConnected and connectionState properties - Add checkConnection() method for active connection testing - Add ensureConnected() method for proactive connection recovery
This commit is contained in:
@@ -70,6 +70,17 @@ public class NatsClient {
|
||||
public var connectedUrl: URL? {
|
||||
connectionHandler?.connectedUrl
|
||||
}
|
||||
|
||||
/// Returns the current connection state
|
||||
public var connectionState: NatsState {
|
||||
connectionHandler?.currentState ?? .closed
|
||||
}
|
||||
|
||||
/// Returns true if the client is currently connected to a server
|
||||
public var isConnected: Bool {
|
||||
connectionHandler?.currentState == .connected
|
||||
}
|
||||
|
||||
internal let allocator = ByteBufferAllocator()
|
||||
internal var buffer: ByteBuffer
|
||||
internal var connectionHandler: ConnectionHandler?
|
||||
@@ -349,4 +360,90 @@ extension NatsClient {
|
||||
await connectionHandler.sendPing(ping)
|
||||
return try await ping.getRoundTripTime()
|
||||
}
|
||||
|
||||
/// Checks if the connection is alive by sending a ping.
|
||||
/// If the connection is not in connected state, returns false.
|
||||
/// If the ping fails, it triggers a reconnect and returns false.
|
||||
///
|
||||
/// - Parameter timeout: The maximum time to wait for a pong response (default: 5 seconds)
|
||||
/// - Returns: true if the connection is alive, false otherwise
|
||||
public func checkConnection(timeout: TimeInterval = 5) async -> Bool {
|
||||
guard let connectionHandler = self.connectionHandler else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Quick state check first
|
||||
guard connectionHandler.currentState == .connected else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Try to send a ping and wait for pong
|
||||
do {
|
||||
let ping = RttCommand.makeFrom(channel: connectionHandler.channel)
|
||||
await connectionHandler.sendPing(ping)
|
||||
|
||||
// Wait for pong with timeout
|
||||
let rtt = try await withThrowingTaskGroup(of: TimeInterval.self) { group in
|
||||
group.addTask {
|
||||
return try await ping.getRoundTripTime()
|
||||
}
|
||||
group.addTask {
|
||||
try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000))
|
||||
throw NatsError.RequestError.timeout
|
||||
}
|
||||
|
||||
// Return first successful result or timeout
|
||||
if let result = try await group.next() {
|
||||
group.cancelAll()
|
||||
return result
|
||||
}
|
||||
throw NatsError.RequestError.timeout
|
||||
}
|
||||
|
||||
logger.debug("Connection check successful, RTT: \(rtt)s")
|
||||
return true
|
||||
} catch {
|
||||
logger.warning("Connection check failed: \(error)")
|
||||
// Connection is broken, the ping failure will trigger reconnect
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// Triggers a reconnect if the connection is not in a healthy state.
|
||||
/// This is useful for proactively recovering from a broken connection.
|
||||
///
|
||||
/// - Returns: true if a reconnect was triggered, false if already connected
|
||||
@discardableResult
|
||||
public func ensureConnected() async throws -> Bool {
|
||||
guard let connectionHandler = self.connectionHandler else {
|
||||
throw NatsError.ClientError.internalError("empty connection handler")
|
||||
}
|
||||
|
||||
if case .closed = connectionHandler.currentState {
|
||||
throw NatsError.ClientError.connectionClosed
|
||||
}
|
||||
|
||||
// If already connected, verify with a ping
|
||||
if connectionHandler.currentState == .connected {
|
||||
let isAlive = await checkConnection(timeout: 3)
|
||||
if isAlive {
|
||||
return false // Already connected, no reconnect needed
|
||||
}
|
||||
// Connection check failed, reconnect will be triggered by ping failure
|
||||
return true
|
||||
}
|
||||
|
||||
// If disconnected or suspended, trigger reconnect
|
||||
if connectionHandler.currentState == .disconnected {
|
||||
connectionHandler.handleReconnect()
|
||||
return true
|
||||
}
|
||||
|
||||
if connectionHandler.currentState == .suspended {
|
||||
try await connectionHandler.resume()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user