@@ -48,21 +48,49 @@ enum ValkeyRequest: Sendable {
48
48
49
49
@usableFromInline
50
50
final class ValkeyChannelHandler : ChannelInboundHandler {
51
+ @usableFromInline
51
52
struct Configuration {
52
53
let authentication : ValkeyClientConfiguration . Authentication ?
54
+ @usableFromInline
55
+ let connectionTimeout : TimeAmount
56
+ @usableFromInline
57
+ let blockingCommandTimeout : TimeAmount
53
58
let clientName : String ?
54
59
}
55
60
@usableFromInline
56
61
struct PendingCommand {
57
62
@usableFromInline
58
- internal init ( promise: ValkeyPromise < RESPToken > , requestID: Int ) {
63
+ internal init ( promise: ValkeyPromise < RESPToken > , requestID: Int , deadline : NIODeadline ) {
59
64
self . promise = promise
60
65
self . requestID = requestID
66
+ self . deadline = deadline
61
67
}
62
68
63
69
var promise : ValkeyPromise < RESPToken >
64
70
let requestID : Int
71
+ let deadline : NIODeadline
65
72
}
73
+
74
+ struct ValkeyDeadlineSchedule : NIOScheduledCallbackHandler {
75
+ let channelHandler : NIOLoopBound < ValkeyChannelHandler >
76
+
77
+ func handleScheduledCallback( eventLoop: some NIOCore . EventLoop ) {
78
+ let channelHandler = self . channelHandler. value
79
+ switch channelHandler. stateMachine. hitDeadline ( now: . now( ) ) {
80
+ case . failPendingCommandsAndClose( let context, let commands) :
81
+ for command in commands {
82
+ command. promise. fail ( ValkeyClientError ( . timeout) )
83
+ }
84
+ channelHandler. closeSubscriptionsAndConnection ( context: context, error: ValkeyClientError ( . timeout) )
85
+ case . reschedule( let deadline) :
86
+ channelHandler. scheduleDeadlineCallback ( deadline: deadline)
87
+ case . clearCallback:
88
+ channelHandler. deadlineCallback = nil
89
+ break
90
+ }
91
+ }
92
+ }
93
+
66
94
@usableFromInline
67
95
typealias OutboundOut = ByteBuffer
68
96
@usableFromInline
@@ -78,11 +106,16 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
78
106
@usableFromInline
79
107
/*private*/ var subscriptions : ValkeySubscriptions
80
108
109
+ @usableFromInline
110
+ private( set) var deadlineCallback : NIOScheduledCallback ?
111
+
81
112
private var decoder : NIOSingleStepByteToMessageProcessor < RESPTokenDecoder >
82
113
private let logger : Logger
83
114
private var isClosed = false
84
- private let configuration : Configuration
115
+ @usableFromInline
116
+ /* private*/ let configuration : Configuration
85
117
118
+ /// Initialize a ValkeyChannelHandler
86
119
init ( configuration: Configuration , eventLoop: EventLoop , logger: Logger ) {
87
120
self . configuration = configuration
88
121
self . eventLoop = eventLoop
@@ -99,12 +132,22 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
99
132
@inlinable
100
133
func write< Command: ValkeyCommand > ( command: Command , continuation: CheckedContinuation < RESPToken , any Error > , requestID: Int ) {
101
134
self . eventLoop. assertInEventLoop ( )
102
- switch self . stateMachine. sendCommand ( PendingCommand ( promise: . swift( continuation) , requestID: requestID) ) {
135
+ let deadline : NIODeadline =
136
+ command. isBlocking ? . now( ) + self . configuration. blockingCommandTimeout : . now( ) + self . configuration. connectionTimeout
137
+ let pendingCommand = PendingCommand (
138
+ promise: . swift( continuation) ,
139
+ requestID: requestID,
140
+ deadline: deadline
141
+ )
142
+ switch self . stateMachine. sendCommand ( pendingCommand) {
103
143
case . sendCommand( let context) :
104
144
self . encoder. reset ( )
105
145
command. encode ( into: & self . encoder)
106
146
let buffer = self . encoder. buffer
107
147
context. writeAndFlush ( self . wrapOutboundOut ( buffer) , promise: nil )
148
+ if self . deadlineCallback == nil {
149
+ self . scheduleDeadlineCallback ( deadline: deadline)
150
+ }
108
151
109
152
case . throwError( let error) :
110
153
continuation. resume ( throwing: error)
@@ -114,21 +157,30 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
114
157
@usableFromInline
115
158
func write( request: ValkeyRequest ) {
116
159
self . eventLoop. assertInEventLoop ( )
160
+ let deadline = . now( ) + self . configuration. connectionTimeout
117
161
switch request {
118
162
case . single( let buffer, let tokenPromise, let requestID) :
119
- let pendingCommand = PendingCommand ( promise: tokenPromise, requestID: requestID)
163
+ let pendingCommand = PendingCommand ( promise: tokenPromise, requestID: requestID, deadline : deadline )
120
164
switch self . stateMachine. sendCommand ( pendingCommand) {
121
165
case . sendCommand( let context) :
122
166
context. writeAndFlush ( self . wrapOutboundOut ( buffer) , promise: nil )
167
+ if self . deadlineCallback == nil {
168
+ scheduleDeadlineCallback ( deadline: deadline)
169
+ }
123
170
case . throwError( let error) :
124
171
tokenPromise. fail ( error)
125
172
}
126
173
127
174
case . multiple( let buffer, let tokenPromises, let requestID) :
128
- let pendingCommands = tokenPromises. map { PendingCommand ( promise: $0, requestID: requestID) }
175
+ let pendingCommands = tokenPromises. map {
176
+ PendingCommand ( promise: $0, requestID: requestID, deadline: deadline)
177
+ }
129
178
switch self . stateMachine. sendCommands ( pendingCommands) {
130
179
case . sendCommand( let context) :
131
180
context. writeAndFlush ( self . wrapOutboundOut ( buffer) , promise: nil )
181
+ if self . deadlineCallback == nil {
182
+ scheduleDeadlineCallback ( deadline: deadline)
183
+ }
132
184
case . throwError( let error) :
133
185
for promise in tokenPromises {
134
186
promise. fail ( error)
@@ -315,7 +367,8 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
315
367
switch token. identifier {
316
368
case . simpleError, . bulkError:
317
369
switch self . stateMachine. receivedResponse ( ) {
318
- case . respond( let command) :
370
+ case . respond( let command, let deadlineAction) :
371
+ self . processDeadlineCallbackAction ( action: deadlineAction)
319
372
command. promise. fail ( ValkeyClientError ( . commandError, message: token. errorString. map { String ( buffer: $0) } ) )
320
373
case . respondAndClose( let command) :
321
374
command. promise. fail ( ValkeyClientError ( . commandError, message: token. errorString. map { String ( buffer: $0) } ) )
@@ -330,7 +383,8 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
330
383
do {
331
384
if try self . subscriptions. notify ( token) == true {
332
385
switch self . stateMachine. receivedResponse ( ) {
333
- case . respond( let command) :
386
+ case . respond( let command, let deadlineAction) :
387
+ self . processDeadlineCallbackAction ( action: deadlineAction)
334
388
command. promise. succeed ( Self . simpleOk)
335
389
case . respondAndClose( let command) :
336
390
command. promise. succeed ( Self . simpleOk)
@@ -356,7 +410,8 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
356
410
. set,
357
411
. attribute:
358
412
switch self . stateMachine. receivedResponse ( ) {
359
- case . respond( let command) :
413
+ case . respond( let command, let deadlineAction) :
414
+ self . processDeadlineCallbackAction ( action: deadlineAction)
360
415
command. promise. succeed ( token)
361
416
case . respondAndClose( let command) :
362
417
command. promise. succeed ( token)
@@ -382,6 +437,26 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
382
437
}
383
438
}
384
439
440
+ @usableFromInline
441
+ func scheduleDeadlineCallback( deadline: NIODeadline ) {
442
+ self . deadlineCallback = try ? self . eventLoop. scheduleCallback (
443
+ at: deadline,
444
+ handler: ValkeyDeadlineSchedule ( channelHandler: . init( self , eventLoop: self . eventLoop) )
445
+ )
446
+ }
447
+
448
+ func processDeadlineCallbackAction( action: StateMachine < ChannelHandlerContext > . DeadlineCallbackAction ) {
449
+ switch action {
450
+ case . cancel:
451
+ self . deadlineCallback? . cancel ( )
452
+ self . deadlineCallback = nil
453
+ case . reschedule( let deadline) :
454
+ self . scheduleDeadlineCallback ( deadline: deadline)
455
+ case . doNothing:
456
+ break
457
+ }
458
+ }
459
+
385
460
private func closeSubscriptionsAndConnection( context: ChannelHandlerContext , error: ( any Error ) ? = nil ) {
386
461
if let error {
387
462
context. fireErrorCaught ( error)
@@ -411,6 +486,7 @@ final class ValkeyChannelHandler: ChannelInboundHandler {
411
486
command. promise. fail ( ValkeyClientError . init ( . connectionClosed) )
412
487
}
413
488
self . subscriptions. close ( error: ValkeyClientError . init ( . connectionClosed) )
489
+ self . deadlineCallback? . cancel ( )
414
490
case . doNothing:
415
491
break
416
492
}
0 commit comments