@@ -42,8 +42,8 @@ enum ValkeyPromise<T: Sendable>: Sendable {
42
42
43
43
@usableFromInline
44
44
enum ValkeyRequest : Sendable {
45
- case single( buffer: ByteBuffer , promise: ValkeyPromise < RESPToken > )
46
- case multiple( buffer: ByteBuffer , promises: [ ValkeyPromise < RESPToken > ] )
45
+ case single( buffer: ByteBuffer , promise: ValkeyPromise < RESPToken > , id : Int )
46
+ case multiple( buffer: ByteBuffer , promises: [ ValkeyPromise < RESPToken > ] , id : Int )
47
47
}
48
48
49
49
@usableFromInline
@@ -53,6 +53,17 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
53
53
let clientName : String ?
54
54
}
55
55
@usableFromInline
56
+ struct PendingCommand {
57
+ @usableFromInline
58
+ internal init ( promise: ValkeyPromise < RESPToken > , requestID: Int ) {
59
+ self . promise = promise
60
+ self . requestID = requestID
61
+ }
62
+
63
+ var promise : ValkeyPromise < RESPToken >
64
+ let requestID : Int
65
+ }
66
+ @usableFromInline
56
67
typealias OutboundOut = ByteBuffer
57
68
@usableFromInline
58
69
typealias InboundIn = ByteBuffer
@@ -61,7 +72,7 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
61
72
@usableFromInline
62
73
/*private*/ let eventLoop : EventLoop
63
74
@usableFromInline
64
- /*private*/ var commands : Deque < ValkeyPromise < RESPToken > >
75
+ /*private*/ var pendingCommands : Deque < PendingCommand >
65
76
@usableFromInline
66
77
/*private*/ var encoder = ValkeyCommandEncoder ( )
67
78
@usableFromInline
@@ -77,7 +88,7 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
77
88
init ( configuration: Configuration , eventLoop: EventLoop , logger: Logger ) {
78
89
self . configuration = configuration
79
90
self . eventLoop = eventLoop
80
- self . commands = . init( )
91
+ self . pendingCommands = . init( )
81
92
self . subscriptions = . init( logger: logger)
82
93
self . decoder = NIOSingleStepByteToMessageProcessor ( RESPTokenDecoder ( ) )
83
94
self . stateMachine = . init( )
@@ -89,15 +100,15 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
89
100
/// - request: Valkey command request
90
101
/// - promise: Promise to fulfill when command is complete
91
102
@inlinable
92
- func write< Command: ValkeyCommand > ( command: Command , continuation: CheckedContinuation < RESPToken , any Error > ) {
103
+ func write< Command: ValkeyCommand > ( command: Command , continuation: CheckedContinuation < RESPToken , any Error > , requestID : Int ) {
93
104
self . eventLoop. assertInEventLoop ( )
94
105
switch self . stateMachine. sendCommand ( ) {
95
106
case . sendCommand( let context) :
96
107
self . encoder. reset ( )
97
108
command. encode ( into: & self . encoder)
98
109
let buffer = self . encoder. buffer
99
110
100
- self . commands . append ( . swift( continuation) )
111
+ self . pendingCommands . append ( . init ( promise : . swift( continuation) , requestID : requestID ) )
101
112
context. writeAndFlush ( self . wrapOutboundOut ( buffer) , promise: nil )
102
113
103
114
case . throwError( let error) :
@@ -108,26 +119,24 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
108
119
@usableFromInline
109
120
func write( request: ValkeyRequest ) {
110
121
self . eventLoop. assertInEventLoop ( )
111
- switch self . stateMachine . sendCommand ( ) {
112
- case . sendCommand ( let context ) :
113
- switch request {
114
- case . single ( let buffer , let tokenPromise ) :
115
- self . commands . append ( tokenPromise)
122
+ switch request {
123
+ case . single ( let buffer , let tokenPromise , let requestID ) :
124
+ switch self . stateMachine . sendCommand ( ) {
125
+ case . sendCommand ( let context ) :
126
+ self . pendingCommands . append ( . init ( promise : tokenPromise, requestID : requestID ) )
116
127
context. writeAndFlush ( self . wrapOutboundOut ( buffer) , promise: nil )
128
+ case . throwError( let error) :
129
+ tokenPromise. fail ( error)
130
+ }
117
131
118
- case . multiple( let buffer, let tokenPromises) :
132
+ case . multiple( let buffer, let tokenPromises, let requestID) :
133
+ switch self . stateMachine. sendCommand ( ) {
134
+ case . sendCommand( let context) :
119
135
for tokenPromise in tokenPromises {
120
- self . commands . append ( tokenPromise)
136
+ self . pendingCommands . append ( . init ( promise : tokenPromise, requestID : requestID ) )
121
137
}
122
138
context. writeAndFlush ( self . wrapOutboundOut ( buffer) , promise: nil )
123
- }
124
-
125
- case . throwError( let error) :
126
- switch request {
127
- case . single( _, let tokenPromise) :
128
- tokenPromise. fail ( error)
129
-
130
- case . multiple( _, let tokenPromises) :
139
+ case . throwError( let error) :
131
140
for promise in tokenPromises {
132
141
promise. fail ( error)
133
142
}
@@ -140,7 +149,8 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
140
149
command: some ValkeyCommand ,
141
150
streamContinuation: ValkeySubscription . Continuation ,
142
151
filters: [ ValkeySubscriptionFilter ] ,
143
- promise: ValkeyPromise < Int >
152
+ promise: ValkeyPromise < Int > ,
153
+ requestID: Int
144
154
) {
145
155
self . eventLoop. assertInEventLoop ( )
146
156
switch self . subscriptions. addSubscription ( continuation: streamContinuation, filters: filters) {
@@ -149,7 +159,7 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
149
159
// But it would be cool to build the subscribe command based on what filters we aren't subscribed to
150
160
self . subscriptions. pushCommand ( filters: subscription. filters)
151
161
let subscriptionID = subscription. id
152
- return self . _send ( command: command) . assumeIsolated ( ) . whenComplete { result in
162
+ return self . _send ( command: command, requestID : requestID ) . assumeIsolated ( ) . whenComplete { result in
153
163
switch result {
154
164
case . success:
155
165
promise. succeed ( subscriptionID)
@@ -168,27 +178,31 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
168
178
/// Remove subscription and if required call UNSUBSCRIBE command
169
179
func unsubscribe(
170
180
id: Int ,
171
- promise: ValkeyPromise < Void >
181
+ promise: ValkeyPromise < Void > ,
182
+ requestID: Int
172
183
) {
173
184
self . eventLoop. assertInEventLoop ( )
174
185
switch self . subscriptions. unsubscribe ( id: id) {
175
186
case . unsubscribe( let channels) :
176
187
self . performUnsubscribe (
177
188
command: UNSUBSCRIBE ( channel: channels) ,
178
189
filters: channels. map { . channel( $0) } ,
179
- promise: promise
190
+ promise: promise,
191
+ requestID: requestID
180
192
)
181
193
case . punsubscribe( let patterns) :
182
194
self . performUnsubscribe (
183
195
command: PUNSUBSCRIBE ( pattern: patterns) ,
184
196
filters: patterns. map { . pattern( $0) } ,
185
- promise: promise
197
+ promise: promise,
198
+ requestID: requestID
186
199
)
187
200
case . sunsubscribe( let shardChannels) :
188
201
self . performUnsubscribe (
189
202
command: SUNSUBSCRIBE ( shardchannel: shardChannels) ,
190
203
filters: shardChannels. map { . shardChannel( $0) } ,
191
- promise: promise
204
+ promise: promise,
205
+ requestID: requestID
192
206
)
193
207
case . doNothing:
194
208
promise. succeed ( ( ) )
@@ -198,10 +212,11 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
198
212
func performUnsubscribe(
199
213
command: some ValkeyCommand ,
200
214
filters: [ ValkeySubscriptionFilter ] ,
201
- promise: ValkeyPromise < Void >
215
+ promise: ValkeyPromise < Void > ,
216
+ requestID: Int
202
217
) {
203
218
self . subscriptions. pushCommand ( filters: filters)
204
- self . _send ( command: command) . assumeIsolated ( ) . whenComplete { result in
219
+ self . _send ( command: command, requestID : requestID ) . assumeIsolated ( ) . whenComplete { result in
205
220
switch result {
206
221
case . success:
207
222
promise. succeed ( ( ) )
@@ -222,7 +237,8 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
222
237
auth: configuration. authentication. map { . init( username: $0. username, password: $0. password) } ,
223
238
clientname: configuration. clientName
224
239
)
225
- )
240
+ ) ,
241
+ requestID: 0
226
242
) . assumeIsolated ( ) . whenComplete { result in
227
243
switch result {
228
244
case . failure( let error) :
@@ -283,27 +299,51 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
283
299
}
284
300
}
285
301
302
+ @usableFromInline
303
+ func cancel( requestID: Int ) {
304
+ self . eventLoop. assertInEventLoop ( )
305
+ // if pending commands include request then we are still waiting for its result.
306
+ // We should cancel that command, cancel all the other pending commands with error
307
+ // code `.connectionClosedDueToCancellation` and close the connection
308
+ if self . pendingCommands. contains ( where: { $0. requestID == requestID } ) {
309
+ switch self . stateMachine. cancel ( ) {
310
+ case . cancelAndCloseConnection( let context) :
311
+ while let command = self . pendingCommands. popFirst ( ) {
312
+ if command. requestID == requestID {
313
+ command. promise. fail ( ValkeyClientError ( . cancelled) )
314
+ } else {
315
+ command. promise. fail ( ValkeyClientError ( . connectionClosedDueToCancellation) )
316
+ }
317
+ }
318
+ context. close ( promise: nil )
319
+
320
+ case . doNothing:
321
+ break
322
+ }
323
+ }
324
+ }
325
+
286
326
func handleToken( context: ChannelHandlerContext , token: RESPToken ) {
287
327
switch token. identifier {
288
328
case . simpleError, . bulkError:
289
- guard let promise = commands . popFirst ( ) else {
329
+ guard let command = pendingCommands . popFirst ( ) else {
290
330
self . failPendingCommandsAndSubscriptionsAndCloseConnection (
291
331
ValkeyClientError ( . unsolicitedToken, message: " Received an error token without having sent a command " ) ,
292
332
context: context
293
333
)
294
334
return
295
335
}
296
- promise. fail ( ValkeyClientError ( . commandError, message: token. errorString. map { String ( buffer: $0) } ) )
336
+ command . promise. fail ( ValkeyClientError ( . commandError, message: token. errorString. map { String ( buffer: $0) } ) )
297
337
298
338
case . push:
299
339
// If subscription notify throws an error then assume something has gone wrong
300
340
// and close the channel with the error
301
341
do {
302
342
if try self . subscriptions. notify ( token) == true {
303
- guard let promise = commands . popFirst ( ) else {
343
+ guard let command = pendingCommands . popFirst ( ) else {
304
344
preconditionFailure ( " Unexpected response " )
305
345
}
306
- promise. succeed ( Self . simpleOk)
346
+ command . promise. succeed ( Self . simpleOk)
307
347
}
308
348
} catch {
309
349
context. close ( mode: . all, promise: nil )
@@ -321,32 +361,32 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
321
361
. map,
322
362
. set,
323
363
. attribute:
324
- guard let promise = commands . popFirst ( ) else {
364
+ guard let command = pendingCommands . popFirst ( ) else {
325
365
self . failPendingCommandsAndSubscriptionsAndCloseConnection (
326
366
ValkeyClientError ( . unsolicitedToken, message: " Received a token without having sent a command " ) ,
327
367
context: context
328
368
)
329
369
return
330
370
}
331
- promise. succeed ( token)
371
+ command . promise. succeed ( token)
332
372
}
333
373
}
334
374
335
375
func handleError( context: ChannelHandlerContext , error: Error ) {
336
376
self . logger. debug ( " ValkeyCommandHandler: ERROR " , metadata: [ " error " : " \( error) " ] )
337
- guard let promise = commands . popFirst ( ) else {
377
+ guard let command = pendingCommands . popFirst ( ) else {
338
378
self . failPendingCommandsAndSubscriptionsAndCloseConnection (
339
379
ValkeyClientError ( . unsolicitedToken, message: " Received an error decoding a token without having sent a command " ) ,
340
380
context: context
341
381
)
342
382
return
343
383
}
344
- promise. fail ( error)
384
+ command . promise. fail ( error)
345
385
}
346
386
347
387
private func failPendingCommandsAndSubscriptions( _ error: any Error ) {
348
- while let promise = self . commands . popFirst ( ) {
349
- promise. fail ( error)
388
+ while let command = self . pendingCommands . popFirst ( ) {
389
+ command . promise. fail ( error)
350
390
}
351
391
self . subscriptions. close ( error: error)
352
392
}
@@ -358,14 +398,14 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
358
398
}
359
399
360
400
// Function used internally by subscribe
361
- func _send< Command: ValkeyCommand > ( command: Command ) -> EventLoopFuture < RESPToken > {
401
+ func _send< Command: ValkeyCommand > ( command: Command , requestID : Int ) -> EventLoopFuture < RESPToken > {
362
402
self . eventLoop. assertInEventLoop ( )
363
403
self . encoder. reset ( )
364
404
command. encode ( into: & self . encoder)
365
405
let buffer = self . encoder. buffer
366
406
367
407
let promise = eventLoop. makePromise ( of: RESPToken . self)
368
- self . write ( request: ValkeyRequest . single ( buffer: buffer, promise: . nio( promise) ) )
408
+ self . write ( request: ValkeyRequest . single ( buffer: buffer, promise: . nio( promise) , id : requestID ) )
369
409
return promise. futureResult
370
410
}
371
411
0 commit comments