@@ -207,7 +207,7 @@ export class AsyncQueue {
207
207
// The last promise in the queue.
208
208
private tail : Promise < unknown > = Promise . resolve ( ) ;
209
209
210
- // A list of retryable operations Retryable operation are run in order and
210
+ // A list of retryable operations. Retryable operations are run in order and
211
211
// retried with backoff.
212
212
private retryableOps : Array < ( ) => Promise < void > > = [ ] ;
213
213
@@ -332,21 +332,33 @@ export class AsyncQueue {
332
332
* reschedules with backoff.
333
333
*/
334
334
private async retryNextOp ( ) : Promise < void > {
335
- const op = this . retryableOps . shift ( ) ;
336
-
337
- if ( op ) {
338
- try {
339
- await op ( ) ;
340
- this . backoff . reset ( ) ;
341
- } catch ( e ) {
342
- if ( isIndexedDbTransactionError ( e ) ) {
343
- logDebug ( LOG_TAG , 'Operation failed with retryable error: ' + e ) ;
344
- this . retryableOps . unshift ( op ) ;
345
- } else {
346
- throw e ; // Failure will be handled by AsyncQueue
347
- }
335
+ if ( this . retryableOps . length === 0 ) {
336
+ return ;
337
+ }
338
+
339
+ try {
340
+ await this . retryableOps [ 0 ] ( ) ;
341
+ this . retryableOps . shift ( ) ;
342
+ this . backoff . reset ( ) ;
343
+ } catch ( e ) {
344
+ if ( isIndexedDbTransactionError ( e ) ) {
345
+ logDebug ( LOG_TAG , 'Operation failed with retryable error: ' + e ) ;
346
+ } else {
347
+ throw e ; // Failure will be handled by AsyncQueue
348
348
}
349
+ }
349
350
351
+ if ( this . retryableOps . length > 0 ) {
352
+ // If there are additional operations, we re-schedule `retryNextOp()`.
353
+ // This is necessary to run retryable operations that failed during
354
+ // their initial attempt since we don't know whether they are already
355
+ // enqueued. If, for example, `op1`, `op2`, `op3` are enqueued and `op1`
356
+ // needs to be re-run, we will run `op1`, `op1`, `op2` using the
357
+ // already enqueued calls to `retryNextOp()`. `op3()` will then run in the
358
+ // call scheduled here.
359
+ // Since `backoffAndRun()` cancels an existing backoff and schedules a
360
+ // new backoff on every call, there is only ever a single additional
361
+ // operation in the queue.
350
362
this . backoff . backoffAndRun ( ( ) => this . retryNextOp ( ) ) ;
351
363
}
352
364
}
0 commit comments