12
12
//
13
13
//===----------------------------------------------------------------------===//
14
14
15
+ import DequeModule
15
16
import NIOCore
16
17
17
18
extension ValkeyChannelHandler {
18
19
@usableFromInline
19
- struct StateMachine < Context> {
20
+ struct StateMachine < Context> : ~ Copyable {
20
21
@usableFromInline
21
- enum State {
22
+ enum State : ~ Copyable {
22
23
case initializing
23
24
case active( ActiveState )
24
- case closing( ClosingState )
25
+ case closing( ActiveState )
25
26
case closed
27
+
28
+ @usableFromInline
29
+ var description : String {
30
+ borrowing get {
31
+ switch self {
32
+ case . initializing: " initializing "
33
+ case . active: " active "
34
+ case . closing: " closing "
35
+ case . closed: " closed "
36
+ }
37
+ }
38
+ }
26
39
}
27
40
@usableFromInline
28
41
var state : State
29
42
30
43
@usableFromInline
31
44
struct ActiveState {
32
45
let context : Context
33
- }
46
+ var pendingCommands : Deque < PendingCommand >
34
47
35
- @usableFromInline
36
- struct ClosingState {
37
- let context : Context
48
+ func cancel( requestID: Int ) -> ( cancel: [ PendingCommand ] , connectionClosedDueToCancellation: [ PendingCommand ] ) {
49
+ var withRequestID = [ PendingCommand] ( )
50
+ var withoutRequestID = [ PendingCommand] ( )
51
+ for command in pendingCommands {
52
+ if command. requestID == requestID {
53
+ withRequestID. append ( command)
54
+ } else {
55
+ withoutRequestID. append ( command)
56
+ }
57
+ }
58
+ return ( withRequestID, withoutRequestID)
59
+ }
38
60
}
39
61
40
62
init ( ) {
41
63
self . state = . initializing
42
64
}
43
65
66
+ private init ( _ state: consuming State ) {
67
+ self . state = state
68
+ }
69
+
44
70
/// handler has become active
45
71
@usableFromInline
46
72
mutating func setActive( context: Context ) {
47
- switch self . state {
73
+ switch consume self. state {
48
74
case . initializing:
49
- self . state = . active( . init( context: context) )
50
- case . active, . closing, . closed:
51
- preconditionFailure ( " Cannot set active state when state is \( self . state) " )
75
+ self = . active( . init( context: context, pendingCommands: [ ] ) )
76
+ case . active:
77
+ preconditionFailure ( " Cannot set active state when state is active " )
78
+ case . closing:
79
+ preconditionFailure ( " Cannot set active state when state is closing " )
80
+ case . closed:
81
+ preconditionFailure ( " Cannot set active state when state is closed " )
52
82
}
53
83
}
54
84
@@ -60,104 +90,203 @@ extension ValkeyChannelHandler {
60
90
61
91
/// handler wants to send a command
62
92
@usableFromInline
63
- func sendCommand( ) -> SendCommandAction {
64
- switch self . state {
93
+ mutating func sendCommand( _ pendingCommand: PendingCommand ) -> SendCommandAction {
94
+ self . sendCommands ( CollectionOfOne ( pendingCommand) )
95
+ }
96
+
97
+ /// handler wants to send pipelined commands
98
+ @usableFromInline
99
+ mutating func sendCommands( _ pendingCommands: some Collection < PendingCommand > ) -> SendCommandAction {
100
+ switch consume self. state {
65
101
case . initializing:
66
102
preconditionFailure ( " Cannot send command when initializing " )
67
- case . active( let state) :
103
+ case . active( var state) :
104
+ state. pendingCommands. append ( contentsOf: pendingCommands)
105
+ self = . active( state)
68
106
return . sendCommand( state. context)
69
- case . closing:
107
+ case . closing( let state) :
108
+ self = . closing( state)
70
109
return . throwError( ValkeyClientError ( . connectionClosing) )
71
110
case . closed:
111
+ self = . closed
72
112
return . throwError( ValkeyClientError ( . connectionClosed) )
73
113
}
74
114
}
75
115
116
+ @usableFromInline
117
+ enum ReceivedResponseAction {
118
+ case respond( PendingCommand )
119
+ case respondAndClose( PendingCommand )
120
+ case closeWithError( Error )
121
+ }
122
+
123
+ /// handler wants to send a command
124
+ @usableFromInline
125
+ mutating func receivedResponse( ) -> ReceivedResponseAction {
126
+ switch consume self. state {
127
+ case . initializing:
128
+ preconditionFailure ( " Cannot send command when initializing " )
129
+ case . active( var state) :
130
+ guard let command = state. pendingCommands. popFirst ( ) else {
131
+ self = . closed
132
+ return . closeWithError( ValkeyClientError ( . unsolicitedToken, message: " Received a token without having sent a command " ) )
133
+ }
134
+ self = . active( state)
135
+ return . respond( command)
136
+ case . closing( var state) :
137
+ guard let command = state. pendingCommands. popFirst ( ) else {
138
+ self = . closed
139
+ return . closeWithError( ValkeyClientError ( . unsolicitedToken, message: " Received a token without having sent a command " ) )
140
+ }
141
+ if state. pendingCommands. count == 0 {
142
+ self = . closed
143
+ return . respondAndClose( command)
144
+ } else {
145
+ self = . closing( state)
146
+ return . respond( command)
147
+ }
148
+ case . closed:
149
+ preconditionFailure ( " Cannot receive command on closed connection " )
150
+ }
151
+ }
152
+
76
153
@usableFromInline
77
154
enum CancelAction {
78
- case cancelAndCloseConnection ( Context )
155
+ case failPendingCommandsAndClose ( Context , cancel : [ PendingCommand ] , closeConnectionDueToCancel : [ PendingCommand ] )
79
156
case doNothing
80
157
}
81
158
82
159
/// handler wants to send a command
83
160
@usableFromInline
84
- mutating func cancel( ) -> CancelAction {
85
- switch self . state {
161
+ mutating func cancel( requestID : Int ) -> CancelAction {
162
+ switch consume self. state {
86
163
case . initializing:
87
164
preconditionFailure ( " Cannot cancel when initializing " )
88
165
case . active( let state) :
89
- self . state = . closed
90
- return . cancelAndCloseConnection( state. context)
166
+ let ( cancel, closeConnectionDueToCancel) = state. cancel ( requestID: requestID)
167
+ if cancel. count > 0 {
168
+ self = . closed
169
+ return . failPendingCommandsAndClose(
170
+ state. context,
171
+ cancel: cancel,
172
+ closeConnectionDueToCancel: closeConnectionDueToCancel
173
+ )
174
+ } else {
175
+ self = . active( state)
176
+ return . doNothing
177
+ }
91
178
case . closing( let state) :
92
- self . state = . closed
93
- return . cancelAndCloseConnection( state. context)
179
+ let ( cancel, closeConnectionDueToCancel) = state. cancel ( requestID: requestID)
180
+ if cancel. count > 0 {
181
+ self = . closed
182
+ return . failPendingCommandsAndClose(
183
+ state. context,
184
+ cancel: cancel,
185
+ closeConnectionDueToCancel: closeConnectionDueToCancel
186
+ )
187
+ } else {
188
+ self = . closing( state)
189
+ return . doNothing
190
+ }
94
191
case . closed:
192
+ self = . closed
95
193
return . doNothing
96
194
}
97
195
}
98
196
99
197
@usableFromInline
100
198
enum GracefulShutdownAction {
101
199
case waitForPendingCommands( Context )
200
+ case closeConnection( Context )
102
201
case doNothing
103
202
}
104
203
/// Want to gracefully shutdown the handler
105
204
@usableFromInline
106
205
mutating func gracefulShutdown( ) -> GracefulShutdownAction {
107
- switch self . state {
206
+ switch consume self. state {
108
207
case . initializing:
109
- self . state = . closed
208
+ self = . closed
110
209
return . doNothing
111
210
case . active( let state) :
112
- self . state = . closing( ClosingState ( context: state. context) )
113
- return . waitForPendingCommands( state. context)
114
- case . closed, . closing:
211
+ if state. pendingCommands. count > 0 {
212
+ self = . closing( . init( context: state. context, pendingCommands: state. pendingCommands) )
213
+ return . waitForPendingCommands( state. context)
214
+ } else {
215
+ self = . closed
216
+ return . closeConnection( state. context)
217
+ }
218
+ case . closing( let state) :
219
+ self = . closing( state)
220
+ return . doNothing
221
+ case . closed:
222
+ self = . closed
115
223
return . doNothing
116
224
}
117
225
}
118
226
119
227
@usableFromInline
120
228
enum CloseAction {
121
- case close ( Context )
229
+ case failPendingCommandsAndClose ( Context , Deque < PendingCommand > )
122
230
case doNothing
123
231
}
124
232
/// Want to close the connection
125
233
@usableFromInline
126
234
mutating func close( ) -> CloseAction {
127
- switch self . state {
235
+ switch consume self. state {
128
236
case . initializing:
129
- self . state = . closed
237
+ self = . closed
130
238
return . doNothing
131
239
case . active( let state) :
132
- self . state = . closed
133
- return . close ( state. context)
240
+ self = . closed
241
+ return . failPendingCommandsAndClose ( state. context, state . pendingCommands )
134
242
case . closing( let state) :
135
- self . state = . closed
136
- return . close ( state. context)
243
+ self = . closed
244
+ return . failPendingCommandsAndClose ( state. context, state . pendingCommands )
137
245
case . closed:
246
+ self = . closed
138
247
return . doNothing
139
248
}
140
249
}
141
250
142
251
@usableFromInline
143
252
enum SetClosedAction {
144
- case failPendingCommands
253
+ case failPendingCommandsAndSubscriptions ( Deque < PendingCommand > )
145
254
case doNothing
146
255
}
147
256
148
257
/// The connection has been closed
149
258
@usableFromInline
150
259
mutating func setClosed( ) -> SetClosedAction {
151
- switch self . state {
260
+ switch consume self. state {
152
261
case . initializing:
153
- self . state = . closed
262
+ self = . closed
154
263
return . doNothing
155
- case . active, . closing:
156
- self . state = . closed
157
- return . failPendingCommands
264
+ case . active( let state) :
265
+ self = . closed
266
+ return . failPendingCommandsAndSubscriptions( state. pendingCommands)
267
+ case . closing( let state) :
268
+ self = . closed
269
+ return . failPendingCommandsAndSubscriptions( state. pendingCommands)
158
270
case . closed:
271
+ self = . closed
159
272
return . doNothing
160
273
}
161
274
}
275
+
276
+ private static var initializing : Self {
277
+ StateMachine ( . initializing)
278
+ }
279
+
280
+ private static func active( _ state: ActiveState ) -> Self {
281
+ StateMachine ( . active( state) )
282
+ }
283
+
284
+ private static func closing( _ state: ActiveState ) -> Self {
285
+ StateMachine ( . closing( state) )
286
+ }
287
+
288
+ private static var closed : Self {
289
+ StateMachine ( . closed)
290
+ }
162
291
}
163
292
}
0 commit comments