1
- import { SinglyLinkedList , DoublyLinkedNode , DoublyLinkedList } from './linked-list' ;
1
+ import { DoublyLinkedNode , DoublyLinkedList , EmptyAwareSinglyLinkedList } from './linked-list' ;
2
2
import encodeCommand from '../RESP/encoder' ;
3
3
import { Decoder , PUSH_TYPE_MAPPING , RESP_TYPES } from '../RESP/decoder' ;
4
4
import { TypeMapping , ReplyUnion , RespVersions , RedisArgument } from '../RESP/types' ;
5
5
import { ChannelListeners , PubSub , PubSubCommand , PubSubListener , PubSubType , PubSubTypeListeners } from './pub-sub' ;
6
- import { AbortError , ErrorReply , TimeoutError } from '../errors' ;
6
+ import { AbortError , ErrorReply , CommandTimeoutDuringMaintananceError , TimeoutError } from '../errors' ;
7
7
import { MonitorCallback } from '.' ;
8
8
9
9
export interface CommandOptions < T = TypeMapping > {
@@ -30,6 +30,7 @@ export interface CommandToWrite extends CommandWaitingForReply {
30
30
timeout : {
31
31
signal : AbortSignal ;
32
32
listener : ( ) => unknown ;
33
+ originalTimeout : number | undefined ;
33
34
} | undefined ;
34
35
}
35
36
@@ -61,19 +62,62 @@ export default class RedisCommandsQueue {
61
62
readonly #respVersion;
62
63
readonly #maxLength;
63
64
readonly #toWrite = new DoublyLinkedList < CommandToWrite > ( ) ;
64
- readonly #waitingForReply = new SinglyLinkedList < CommandWaitingForReply > ( ) ;
65
+ readonly #waitingForReply = new EmptyAwareSinglyLinkedList < CommandWaitingForReply > ( ) ;
65
66
readonly #onShardedChannelMoved;
66
67
#chainInExecution: symbol | undefined ;
67
68
readonly decoder ;
68
69
readonly #pubSub = new PubSub ( ) ;
69
70
70
71
#pushHandlers: PushHandler [ ] = [ this . #onPush. bind ( this ) ] ;
72
+
73
+ #inMaintenance = false ;
74
+
75
+ set inMaintenance ( value : boolean ) {
76
+ this . #inMaintenance = value ;
77
+ }
78
+
79
+ #maintenanceCommandTimeout: number | undefined
80
+
81
+ setMaintenanceCommandTimeout ( ms : number | undefined ) {
82
+ // Prevent possible api misuse
83
+ if ( this . #maintenanceCommandTimeout === ms ) return ;
84
+
85
+ this . #maintenanceCommandTimeout = ms ;
86
+
87
+ let counter = 0 ;
88
+
89
+ // Overwrite timeouts of all eligible toWrite commands
90
+ for ( const node of this . #toWrite. nodes ( ) ) {
91
+ const command = node . value ;
92
+
93
+ // Remove timeout listener if it exists
94
+ RedisCommandsQueue . #removeTimeoutListener( command )
95
+
96
+ // Determine newTimeout
97
+ const newTimeout = this . #maintenanceCommandTimeout ?? command . timeout ?. originalTimeout ;
98
+ // if no timeout is given and the command didnt have any timeout before, skip
99
+ if ( ! newTimeout ) return ;
100
+
101
+ counter ++ ;
102
+
103
+ // Overwrite the command's timeout
104
+ const signal = AbortSignal . timeout ( newTimeout ) ;
105
+ command . timeout = {
106
+ signal,
107
+ listener : ( ) => {
108
+ this . #toWrite. remove ( node ) ;
109
+ command . reject ( this . #inMaintenance ? new CommandTimeoutDuringMaintananceError ( newTimeout ) : new TimeoutError ( ) ) ;
110
+ } ,
111
+ originalTimeout : command . timeout ?. originalTimeout
112
+ } ;
113
+ signal . addEventListener ( 'abort' , command . timeout . listener , { once : true } ) ;
114
+ } ;
115
+ }
116
+
71
117
get isPubSubActive ( ) {
72
118
return this . #pubSub. isActive ;
73
119
}
74
120
75
- #invalidateCallback?: ( key : RedisArgument | null ) => unknown ;
76
-
77
121
constructor (
78
122
respVersion : RespVersions ,
79
123
maxLength : number | null | undefined ,
@@ -174,15 +218,19 @@ export default class RedisCommandsQueue {
174
218
typeMapping : options ?. typeMapping
175
219
} ;
176
220
177
- const timeout = options ?. timeout ;
221
+ // If #maintenanceCommandTimeout was explicitly set, we should
222
+ // use it instead of the timeout provided by the command
223
+ const timeout = this . #maintenanceCommandTimeout || options ?. timeout
178
224
if ( timeout ) {
225
+
179
226
const signal = AbortSignal . timeout ( timeout ) ;
180
227
value . timeout = {
181
228
signal,
182
229
listener : ( ) => {
183
230
this . #toWrite. remove ( node ) ;
184
- value . reject ( new TimeoutError ( ) ) ;
185
- }
231
+ value . reject ( this . #inMaintenance ? new CommandTimeoutDuringMaintananceError ( timeout ) : new TimeoutError ( ) ) ;
232
+ } ,
233
+ originalTimeout : options ?. timeout
186
234
} ;
187
235
signal . addEventListener ( 'abort' , value . timeout . listener , { once : true } ) ;
188
236
}
@@ -438,7 +486,7 @@ export default class RedisCommandsQueue {
438
486
}
439
487
440
488
static #removeTimeoutListener( command : CommandToWrite ) {
441
- command . timeout ! . signal . removeEventListener ( 'abort' , command . timeout ! . listener ) ;
489
+ command . timeout ? .signal . removeEventListener ( 'abort' , command . timeout ! . listener ) ;
442
490
}
443
491
444
492
static #flushToWrite( toBeSent : CommandToWrite , err : Error ) {
0 commit comments