@@ -93,6 +93,42 @@ export type OnMessageResponse = {
93
93
hasSyncedPastLastReconnect : boolean ;
94
94
} ;
95
95
96
+ const serverDisconnectErrors = {
97
+ // A known error, e.g. during a restart or push
98
+ InternalServerError : { timeout : 100 } ,
99
+ // ErrorMetadata::overloaded() messages that we realy should back off
100
+ SubscriptionsWorkerFullError : { timeout : 3000 } ,
101
+ TooManyConcurrentRequests : { timeout : 3000 } ,
102
+ CommitterFullError : { timeout : 3000 } ,
103
+ AwsTooManyRequestsException : { timeout : 3000 } ,
104
+ ExecuteFullError : { timeout : 3000 } ,
105
+ SystemTimeoutError : { timeout : 3000 } ,
106
+ ExpiredInQueue : { timeout : 3000 } ,
107
+ // More ErrorMetadata::overloaded() that typically indicate a deploy just happened
108
+ VectorIndexesUnavailable : { timeout : 1000 } ,
109
+ SearchIndexesUnavailable : { timeout : 1000 } ,
110
+ // More ErrorMeatadata::overloaded()
111
+ VectorIndexTooLarge : { timeout : 3000 } ,
112
+ SearchIndexTooLarge : { timeout : 3000 } ,
113
+ TooManyWritesInTimePeriod : { timeout : 3000 } ,
114
+ } as const satisfies Record < string , { timeout : number } > ;
115
+
116
+ type ServerDisconnectError = keyof typeof serverDisconnectErrors | "Unknown" ;
117
+
118
+ function classifyDisconnectError ( s ?: string ) : ServerDisconnectError {
119
+ if ( s === undefined ) return "Unknown" ;
120
+ // startsWith so more info could be at the end (although currently there isn't)
121
+
122
+ for ( const prefix of Object . keys (
123
+ serverDisconnectErrors ,
124
+ ) as ServerDisconnectError [ ] ) {
125
+ if ( s . startsWith ( prefix ) ) {
126
+ return prefix ;
127
+ }
128
+ }
129
+ return "Unknown" ;
130
+ }
131
+
96
132
/**
97
133
* A wrapper around a websocket that handles errors, reconnection, and message
98
134
* parsing.
@@ -102,10 +138,14 @@ export class WebSocketManager {
102
138
103
139
private connectionCount : number ;
104
140
private _hasEverConnected : boolean = false ;
105
- private lastCloseReason : string | null ;
141
+ private lastCloseReason :
142
+ | "InitialConnect"
143
+ | "OnCloseInvoked"
144
+ | ( string & { } ) // a full serverErrorReason (not just the prefix) or a new one
145
+ | null ;
106
146
107
147
/** Upon HTTPS/WSS failure, the first jittered backoff duration, in ms. */
108
- private readonly initialBackoff : number ;
148
+ private readonly defaultInitialBackoff : number ;
109
149
110
150
/** We backoff exponentially, but we need to cap that--this is the jittered max. */
111
151
private readonly maxBackoff : number ;
@@ -143,7 +183,8 @@ export class WebSocketManager {
143
183
this . connectionCount = 0 ;
144
184
this . lastCloseReason = "InitialConnect" ;
145
185
146
- this . initialBackoff = 100 ;
186
+ // backoff for unknown errors
187
+ this . defaultInitialBackoff = 100 ;
147
188
this . maxBackoff = 16000 ;
148
189
this . retries = 0 ;
149
190
@@ -253,11 +294,8 @@ export class WebSocketManager {
253
294
}
254
295
this . logger . log ( msg ) ;
255
296
}
256
- if ( event . reason ?. includes ( "SubscriptionsWorkerFullError" ) ) {
257
- this . scheduleReconnect ( "SubscriptionsWorkerFullError" ) ;
258
- } else {
259
- this . scheduleReconnect ( "unknown" ) ;
260
- }
297
+ const reason = classifyDisconnectError ( event . reason ) ;
298
+ this . scheduleReconnect ( reason ) ;
261
299
return ;
262
300
} ;
263
301
}
@@ -324,9 +362,7 @@ export class WebSocketManager {
324
362
} , this . serverInactivityThreshold ) ;
325
363
}
326
364
327
- private scheduleReconnect (
328
- reason : "client" | "unknown" | "SubscriptionsWorkerFullError" ,
329
- ) {
365
+ private scheduleReconnect ( reason : "client" | ServerDisconnectError ) {
330
366
this . socket = { state : "disconnected" } ;
331
367
const backoff = this . nextBackoff ( reason ) ;
332
368
this . logger . log ( `Attempting reconnect in ${ backoff } ms` ) ;
@@ -549,11 +585,14 @@ export class WebSocketManager {
549
585
this . logger . logVerbose ( message ) ;
550
586
}
551
587
552
- private nextBackoff (
553
- reason : "client" | "unknown" | "SubscriptionsWorkerFullError" ,
554
- ) : number {
555
- const initialBackoff =
556
- reason === "SubscriptionsWorkerFullError" ? 3000 : this . initialBackoff ;
588
+ private nextBackoff ( reason : "client" | ServerDisconnectError ) : number {
589
+ const initialBackoff : number =
590
+ reason === "client"
591
+ ? this . defaultInitialBackoff
592
+ : reason === "Unknown"
593
+ ? this . defaultInitialBackoff
594
+ : serverDisconnectErrors [ reason ] . timeout ;
595
+
557
596
const baseBackoff = initialBackoff * Math . pow ( 2 , this . retries ) ;
558
597
this . retries += 1 ;
559
598
const actualBackoff = Math . min ( baseBackoff , this . maxBackoff ) ;
0 commit comments