2
2
//
3
3
// This source file is part of the RediStack open source project
4
4
//
5
- // Copyright (c) 2019 RediStack project authors
5
+ // Copyright (c) 2019-2020 RediStack project authors
6
6
// Licensed under Apache License v2.0
7
7
//
8
8
// See LICENSE.txt for license information
@@ -37,6 +37,7 @@ public struct RedisCommand {
37
37
public final class RedisCommandHandler {
38
38
/// FIFO queue of promises waiting to receive a response value from a sent command.
39
39
private var commandResponseQueue : CircularBuffer < EventLoopPromise < RESPValue > >
40
+ private var state : State = . default
40
41
41
42
deinit {
42
43
if !self . commandResponseQueue. isEmpty {
@@ -50,31 +51,45 @@ public final class RedisCommandHandler {
50
51
public init ( initialQueueCapacity: Int = 3 ) {
51
52
self . commandResponseQueue = CircularBuffer ( initialCapacity: initialQueueCapacity)
52
53
}
54
+
55
+ private enum State {
56
+ case `default`, error( Error )
57
+ }
53
58
}
54
59
55
60
// MARK: ChannelInboundHandler
56
61
57
62
extension RedisCommandHandler : ChannelInboundHandler {
58
- /// See `NIO.ChannelInboundHandler.InboundIn`
59
63
public typealias InboundIn = RESPValue
60
64
61
- /// Invoked by SwiftNIO when an error has been thrown. The command queue will be drained, with each promise in the queue being failed with the error thrown.
65
+ /// Invoked by SwiftNIO when an error has been thrown. The command queue will be drained
66
+ /// with each promise in the queue being failed with the error thrown.
62
67
///
63
68
/// See `NIO.ChannelInboundHandler.errorCaught(context:error:)`
64
69
/// - Important: This will also close the socket connection to Redis.
65
- /// - Note:`RedisMetrics.commandFailureCount` is **not** incremented from this error.
66
- ///
67
- /// A `Logging.LogLevel.critical` message will be written with the caught error.
70
+ /// - Note:`RedisMetrics.commandFailureCount` is **not** incremented from this method.
68
71
public func errorCaught( context: ChannelHandlerContext , error: Error ) {
72
+ self . _failCommandQueue ( because: error)
73
+ context. close ( promise: nil )
74
+ }
75
+
76
+ /// Invoked by SwiftNIO when the channel's active state has changed, such as when it is closed. The command queue will be drained
77
+ /// with each promise in the queue being failed from a connection closed error.
78
+ ///
79
+ /// See `NIO.ChannelInboundHandler.channelInactive(context:)`
80
+ /// - Note: `RedisMetrics.commandFailureCount` is **not** incremented from this method.
81
+ public func channelInactive( context: ChannelHandlerContext ) {
82
+ self . _failCommandQueue ( because: RedisClientError . connectionClosed)
83
+ }
84
+
85
+ private func _failCommandQueue( because error: Error ) {
69
86
let queue = self . commandResponseQueue
70
-
71
87
self . commandResponseQueue. removeAll ( )
72
88
queue. forEach { $0. fail ( error) }
73
-
74
- context. close ( promise: nil )
75
89
}
76
90
77
91
/// Invoked by SwiftNIO when a read has been fired from earlier in the response chain.
92
+ ///
78
93
/// This forwards the decoded `RESPValue` response message to the promise waiting to be fulfilled at the front of the command queue.
79
94
/// - Note: `RedisMetrics.commandFailureCount` and `RedisMetrics.commandSuccessCount` are incremented from this method.
80
95
///
@@ -94,6 +109,11 @@ extension RedisCommandHandler: ChannelInboundHandler {
94
109
RedisMetrics . commandSuccessCount. increment ( )
95
110
}
96
111
}
112
+
113
+ // public func channelUnregistered(context: ChannelHandlerContext) {
114
+ // self._drainCommandQueue(because: RedisClientError.connectionClosed)
115
+ // }
116
+
97
117
}
98
118
99
119
// MARK: ChannelOutboundHandler
@@ -105,16 +125,23 @@ extension RedisCommandHandler: ChannelOutboundHandler {
105
125
public typealias OutboundOut = RESPValue
106
126
107
127
/// Invoked by SwiftNIO when a `write` has been requested on the `Channel`.
128
+ ///
108
129
/// This unwraps a `RedisCommand`, storing the `NIO.EventLoopPromise` in a command queue,
109
130
/// to fulfill later with the response to the command that is about to be sent through the `NIO.Channel`.
110
131
///
111
132
/// See `NIO.ChannelOutboundHandler.write(context:data:promise:)`
112
133
public func write( context: ChannelHandlerContext , data: NIOAny , promise: EventLoopPromise < Void > ? ) {
113
134
let commandContext = self . unwrapOutboundIn ( data)
114
- self . commandResponseQueue. append ( commandContext. responsePromise)
115
- context. write (
116
- self . wrapOutboundOut ( commandContext. message) ,
117
- promise: promise
118
- )
135
+
136
+ switch self . state {
137
+ case let . error( e) : commandContext. responsePromise. fail ( e)
138
+
139
+ case . default:
140
+ self . commandResponseQueue. append ( commandContext. responsePromise)
141
+ context. write (
142
+ self . wrapOutboundOut ( commandContext. message) ,
143
+ promise: promise
144
+ )
145
+ }
119
146
}
120
147
}
0 commit comments