Skip to content

Commit 0ee1207

Browse files
Feedback
1 parent 2187e60 commit 0ee1207

File tree

2 files changed

+27
-15
lines changed

2 files changed

+27
-15
lines changed

packages/firestore/src/util/async_queue.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ export class AsyncQueue {
207207
// The last promise in the queue.
208208
private tail: Promise<unknown> = Promise.resolve();
209209

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
211211
// retried with backoff.
212212
private retryableOps: Array<() => Promise<void>> = [];
213213

@@ -332,21 +332,33 @@ export class AsyncQueue {
332332
* reschedules with backoff.
333333
*/
334334
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
348348
}
349+
}
349350

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.
350362
this.backoff.backoffAndRun(() => this.retryNextOp());
351363
}
352364
}

packages/firestore/test/unit/util/async_queue.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ describe('AsyncQueue', () => {
312312
expect(completedSteps).to.deep.equal([1, 1, 2]);
313313
});
314314

315-
it('Doesn not delay retryable operations that succeed', async () => {
315+
it('Does not delay retryable operations that succeed', async () => {
316316
const queue = new AsyncQueue();
317317
const completedSteps: number[] = [];
318318
const doStep = (n: number): void => {

0 commit comments

Comments
 (0)