@@ -184,12 +184,17 @@ public final class RedisConnection: RedisClient {
184
184
self . channel. closeFuture. whenSuccess {
185
185
// if our state is still open, that means we didn't cause the closeFuture to resolve.
186
186
// update state, metrics, and logging
187
- guard self . state. isConnected else { return }
188
-
187
+ let oldState = self . state
189
188
self . state = . closed
190
- self . defaultLogger. warning ( " connection was closed unexpectedly " )
191
189
RedisMetrics . activeConnectionCount. decrement ( )
192
- self . onUnexpectedClosure ? ( )
190
+
191
+ switch oldState {
192
+ case . shuttingDown, . closed:
193
+ break
194
+ case . open, . pubsub:
195
+ self . defaultLogger. warning ( " connection was closed unexpectedly " )
196
+ self . onUnexpectedClosure ? ( )
197
+ }
193
198
}
194
199
195
200
self . defaultLogger. trace ( " connection created " )
@@ -300,65 +305,23 @@ extension RedisConnection {
300
305
301
306
// we're now in a shutdown state, starting with the command queue.
302
307
self . state = . shuttingDown
303
-
304
- let notification = self . sendQuitCommand ( logger: logger) // send "QUIT" so that all the responses are written out
305
- . flatMap { self . closeChannel ( ) } // close the channel from our end
306
- . hop ( to: finalEventLoop)
308
+
309
+ // Inform ChannelHandler about close intent using "RedisGracefulConnectionCloseEvent"
310
+ let promise = finalEventLoop. makePromise ( of: Void . self)
311
+ let notification = promise. futureResult
312
+ self . channel. triggerUserOutboundEvent ( RedisGracefulConnectionCloseEvent ( ) , promise: promise)
307
313
308
314
notification. whenFailure {
309
315
logger. warning ( " failed to close connection " , metadata: [
310
316
RedisLogging . MetadataKeys. error: " \( $0) "
311
317
] )
312
318
}
313
319
notification. whenSuccess {
314
- self . state = . closed
315
320
logger. trace ( " connection is now closed " )
316
- RedisMetrics . activeConnectionCount. decrement ( )
317
321
}
318
322
319
323
return notification
320
324
}
321
-
322
- /// Bypasses everything for a normal command and explicitly just sends a "QUIT" command to Redis.
323
- /// - Note: If the command fails, the `NIO.EventLoopFuture` will still succeed - as it's not critical for the command to succeed.
324
- private func sendQuitCommand( logger: Logger ) -> EventLoopFuture < Void > {
325
- let payload : RedisCommandHandler . OutboundCommandPayload = (
326
- RedisCommand< Void> ( keyword: " QUIT " , arguments: [ ] ) . serialized ( ) ,
327
- self . eventLoop. makePromise ( )
328
- )
329
-
330
- logger. trace ( " sending QUIT command " )
331
-
332
- return self . channel
333
- . writeAndFlush ( payload) // write the command
334
- . flatMap { payload. responsePromise. futureResult } // chain the callback to the response's
335
- . map { _ in logger. trace ( " sent QUIT command " ) } // ignore the result's value
336
- . recover { _ in logger. debug ( " recovered from error sending QUIT " ) } // if there's an error, just return to void
337
- }
338
-
339
- /// Attempts to close the `NIO.Channel`.
340
- /// SwiftNIO throws a `NIO.EventLoopError.shutdown` if the channel is already closed,
341
- /// so that case is captured to let this method's `NIO.EventLoopFuture` still succeed.
342
- private func closeChannel( ) -> EventLoopFuture < Void > {
343
- let promise = self . channel. eventLoop. makePromise ( of: Void . self)
344
-
345
- self . channel. close ( promise: promise)
346
-
347
- // if we succeed, great, if not - check the error that happened
348
- return promise. futureResult
349
- . flatMapError { error in
350
- guard let e = error as? EventLoopError else {
351
- return self . eventLoop. makeFailedFuture ( error)
352
- }
353
-
354
- // if the error is that the channel is already closed, great - just succeed.
355
- // otherwise, fail the chain
356
- switch e {
357
- case . shutdown: return self . eventLoop. makeSucceededFuture ( ( ) )
358
- default : return self . eventLoop. makeFailedFuture ( e)
359
- }
360
- }
361
- }
362
325
}
363
326
364
327
// MARK: Logging
0 commit comments