Skip to content

Commit 9982004

Browse files
authored
Treat ABORTED writes as retryable (#1456)
1 parent d63df58 commit 9982004

File tree

4 files changed

+35
-9
lines changed

4 files changed

+35
-9
lines changed

packages/firestore/src/remote/remote_store.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
} from '../model/mutation_batch';
2828
import { emptyByteString } from '../platform/platform';
2929
import { assert } from '../util/assert';
30-
import { Code, FirestoreError } from '../util/error';
30+
import { FirestoreError } from '../util/error';
3131
import * as log from '../util/log';
3232
import * as objUtils from '../util/obj';
3333

@@ -41,7 +41,7 @@ import {
4141
PersistentWriteStream
4242
} from './persistent_stream';
4343
import { RemoteSyncer } from './remote_syncer';
44-
import { isPermanentError } from './rpc_error';
44+
import { isPermanentError, isPermanentWriteError } from './rpc_error';
4545
import {
4646
DocumentWatchChange,
4747
ExistenceFilterChange,
@@ -628,9 +628,10 @@ export class RemoteStore implements TargetMetadataProvider {
628628
}
629629

630630
private async handleHandshakeError(error: FirestoreError): Promise<void> {
631-
// Reset the token if it's a permanent error or the error code is
632-
// ABORTED, signaling the write stream is no longer valid.
633-
if (isPermanentError(error.code) || error.code === Code.ABORTED) {
631+
// Reset the token if it's a permanent error, signaling the write stream is
632+
// no longer valid. Note that the handshake does not count as a write: see
633+
// comments on isPermanentWriteError for details.
634+
if (isPermanentError(error.code)) {
634635
log.debug(
635636
LOG_TAG,
636637
'RemoteStore error before completed handshake; resetting stream token: ',
@@ -648,7 +649,9 @@ export class RemoteStore implements TargetMetadataProvider {
648649
}
649650

650651
private async handleWriteError(error: FirestoreError): Promise<void> {
651-
if (isPermanentError(error.code)) {
652+
// Only handle permanent errors here. If it's transient, just let the retry
653+
// logic kick in.
654+
if (isPermanentWriteError(error.code)) {
652655
// This was a permanent error, the request itself was the problem
653656
// so it's not going to succeed if we resend it.
654657
const batch = this.writePipeline.shift()!;

packages/firestore/src/remote/rpc_error.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ enum RpcCode {
4848
DATA_LOSS = 15
4949
}
5050

51+
/**
52+
* Determines whether an error code represents a permanent error when received
53+
* in response to a non-write operation.
54+
*
55+
* See isPermanentWriteError for classifying write errors.
56+
*/
5157
export function isPermanentError(code: Code): boolean {
5258
switch (code) {
5359
case Code.OK:
@@ -80,6 +86,22 @@ export function isPermanentError(code: Code): boolean {
8086
}
8187
}
8288

89+
/**
90+
* Determines whether an error code represents a permanent error when received
91+
* in response to a write operation.
92+
*
93+
* Write operations must be handled specially because as of b/119437764, ABORTED
94+
* errors on the write stream should be retried too (even though ABORTED errors
95+
* are not generally retryable).
96+
*
97+
* Note that during the initial handshake on the write stream an ABORTED error
98+
* signals that we should discard our stream token (i.e. it is permanent). This
99+
* means a handshake error should be classified with isPermanentError, above.
100+
*/
101+
export function isPermanentWriteError(code: Code): boolean {
102+
return isPermanentError(code) && code !== Code.ABORTED;
103+
}
104+
83105
/**
84106
* Maps an error Code from a GRPC status identifier like 'NOT_FOUND'.
85107
*

packages/firestore/test/unit/specs/spec_builder.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
import { DocumentKey } from '../../../src/model/document_key';
2626
import { JsonObject } from '../../../src/model/field_value';
2727
import {
28-
isPermanentError,
28+
isPermanentWriteError,
2929
mapCodeFromRpcCode,
3030
mapRpcCodeFromCode
3131
} from '../../../src/remote/rpc_error';
@@ -504,7 +504,8 @@ export class SpecBuilder {
504504

505505
// If this is a permanent error, the write is not expected to be sent
506506
// again.
507-
const isPermanentFailure = isPermanentError(mapCodeFromRpcCode(error.code));
507+
const code = mapCodeFromRpcCode(error.code);
508+
const isPermanentFailure = isPermanentWriteError(code);
508509
const keepInQueue =
509510
options.keepInQueue !== undefined
510511
? options.keepInQueue

packages/firestore/test/unit/specs/write_spec.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,6 @@ describeSpec('Writes:', [], () => {
633633
Code.ALREADY_EXISTS,
634634
Code.PERMISSION_DENIED,
635635
Code.FAILED_PRECONDITION,
636-
Code.ABORTED,
637636
Code.OUT_OF_RANGE,
638637
Code.UNIMPLEMENTED,
639638
Code.DATA_LOSS
@@ -697,6 +696,7 @@ describeSpec('Writes:', [], () => {
697696
);
698697

699698
for (const code of [
699+
Code.ABORTED,
700700
Code.CANCELLED,
701701
Code.UNKNOWN,
702702
Code.DEADLINE_EXCEEDED,

0 commit comments

Comments
 (0)