@@ -170,12 +170,17 @@ public final class RedisConnection: RedisClient, RedisClientWithUserContext {
170170 self . channel. closeFuture. whenSuccess {
171171 // if our state is still open, that means we didn't cause the closeFuture to resolve.
172172 // update state, metrics, and logging
173- guard self . state. isConnected else { return }
174-
173+ let oldState = self . state
175174 self . state = . closed
176- self . logger. error ( " connection was closed unexpectedly " )
177175 RedisMetrics . activeConnectionCount. decrement ( )
178- self . onUnexpectedClosure ? ( )
176+
177+ switch oldState {
178+ case . shuttingDown, . closed:
179+ break
180+ case . open, . pubsub:
181+ logger. warning ( " connection was closed unexpectedly " )
182+ self . onUnexpectedClosure ? ( )
183+ }
179184 }
180185
181186 self . logger. trace ( " connection created " )
@@ -333,62 +338,21 @@ extension RedisConnection {
333338 // we're now in a shutdown state, starting with the command queue.
334339 self . state = . shuttingDown
335340
336- let notification = self . sendQuitCommand ( logger: logger) // send "QUIT" so that all the responses are written out
337- . flatMap { self . closeChannel ( ) } // close the channel from our end
341+ // Inform ChannelHandler about close intent using "RedisGracefulConnectionCloseEvent"
342+ let closePromise = self . eventLoop. makePromise ( of: Void . self)
343+ let closeFuture = closePromise. futureResult
344+ self . channel. triggerUserOutboundEvent ( RedisGracefulConnectionCloseEvent ( ) , promise: closePromise)
338345
339- notification . whenFailure {
346+ closeFuture . whenFailure {
340347 logger. error ( " error while closing connection " , metadata: [
341348 RedisLogging . MetadataKeys. error: " \( $0) "
342349 ] )
343350 }
344- notification. whenSuccess {
345- self . state = . closed
351+ closeFuture. whenSuccess {
346352 logger. trace ( " connection is now closed " )
347- RedisMetrics . activeConnectionCount. decrement ( )
348353 }
349354
350- return notification
351- }
352-
353- /// Bypasses everything for a normal command and explicitly just sends a "QUIT" command to Redis.
354- /// - Note: If the command fails, the `NIO.EventLoopFuture` will still succeed - as it's not critical for the command to succeed.
355- private func sendQuitCommand( logger: Logger ) -> EventLoopFuture < Void > {
356- let promise = channel. eventLoop. makePromise ( of: RESPValue . self)
357- let command = RedisCommand (
358- message: . array( [ RESPValue ( bulk: " QUIT " ) ] ) ,
359- responsePromise: promise
360- )
361-
362- logger. trace ( " sending QUIT command " )
363-
364- return channel. writeAndFlush ( command) // write the command
365- . flatMap { promise. futureResult } // chain the callback to the response's
366- . map { _ in logger. trace ( " sent QUIT command " ) } // ignore the result's value
367- . recover { _ in logger. debug ( " recovered from error sending QUIT " ) } // if there's an error, just return to void
368- }
369-
370- /// Attempts to close the `NIO.Channel`.
371- /// SwiftNIO throws a `NIO.EventLoopError.shutdown` if the channel is already closed,
372- /// so that case is captured to let this method's `NIO.EventLoopFuture` still succeed.
373- private func closeChannel( ) -> EventLoopFuture < Void > {
374- let promise = self . channel. eventLoop. makePromise ( of: Void . self)
375-
376- self . channel. close ( promise: promise)
377-
378- // if we succeed, great, if not - check the error that happened
379- return promise. futureResult
380- . flatMapError { error in
381- guard let e = error as? EventLoopError else {
382- return self . eventLoop. makeFailedFuture ( error)
383- }
384-
385- // if the error is that the channel is already closed, great - just succeed.
386- // otherwise, fail the chain
387- switch e {
388- case . shutdown: return self . eventLoop. makeSucceededFuture ( ( ) )
389- default : return self . eventLoop. makeFailedFuture ( e)
390- }
391- }
355+ return closeFuture
392356 }
393357}
394358
0 commit comments