Skip to content

Commit 5cf8e53

Browse files
Make applyRemoteEvent idempotent (#2263)
1 parent 7134b9c commit 5cf8e53

10 files changed

+259
-182
lines changed

packages/firestore/src/local/indexeddb_index_manager.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,28 @@ export class IndexedDbIndexManager implements IndexManager {
4040
*/
4141
private collectionParentsCache = new MemoryCollectionParentIndex();
4242

43+
/**
44+
* Adds a new entry to the collection parent index.
45+
*
46+
* Repeated calls for the same collectionPath should be avoided within a
47+
* transaction as IndexedDbIndexManager only caches writes once a transaction
48+
* has been committed.
49+
*/
4350
addToCollectionParentIndex(
4451
transaction: PersistenceTransaction,
4552
collectionPath: ResourcePath
4653
): PersistencePromise<void> {
4754
assert(collectionPath.length % 2 === 1, 'Expected a collection path.');
48-
if (this.collectionParentsCache.add(collectionPath)) {
49-
assert(collectionPath.length >= 1, 'Invalid collection path.');
55+
if (!this.collectionParentsCache.has(collectionPath)) {
5056
const collectionId = collectionPath.lastSegment();
5157
const parentPath = collectionPath.popLast();
58+
59+
transaction.addOnCommittedListener(() => {
60+
// Add the collection to the in memory cache only if the transaction was
61+
// successfully committed.
62+
this.collectionParentsCache.add(collectionPath);
63+
});
64+
5265
return collectionParentsStore(transaction).put({
5366
collectionId,
5467
parent: encode(parentPath)

packages/firestore/src/local/indexeddb_mutation_queue.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -181,23 +181,28 @@ export class IndexedDbMutationQueue implements MutationQueue {
181181
this.documentKeysByBatchId[batchId] = batch.keys();
182182

183183
const promises: Array<PersistencePromise<void>> = [];
184+
let collectionParents = new SortedSet<ResourcePath>((l, r) =>
185+
primitiveComparator(l.canonicalString(), r.canonicalString())
186+
);
184187
for (const mutation of mutations) {
185188
const indexKey = DbDocumentMutation.key(
186189
this.userId,
187190
mutation.key.path,
188191
batchId
189192
);
193+
collectionParents = collectionParents.add(mutation.key.path.popLast());
190194
promises.push(mutationStore.put(dbBatch));
191195
promises.push(
192196
documentStore.put(indexKey, DbDocumentMutation.PLACEHOLDER)
193197
);
198+
}
199+
200+
collectionParents.forEach(parent => {
194201
promises.push(
195-
this.indexManager.addToCollectionParentIndex(
196-
transaction,
197-
mutation.key.path.popLast()
198-
)
202+
this.indexManager.addToCollectionParentIndex(transaction, parent)
199203
);
200-
}
204+
});
205+
201206
return PersistencePromise.waitFor(promises).next(() => batch);
202207
});
203208
}

packages/firestore/src/local/indexeddb_persistence.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,10 @@ export class IndexedDbPersistence implements Persistence {
760760
this.listenSequence.next()
761761
);
762762

763-
if (mode === 'readwrite-primary') {
763+
if (
764+
mode === 'readwrite-primary' ||
765+
mode === 'readwrite-primary-idempotent'
766+
) {
764767
// While we merely verify that we have (or can acquire) the lease
765768
// immediately, we wait to extend the primary lease until after
766769
// executing transactionOperation(). This ensures that even if the

packages/firestore/src/local/indexeddb_query_cache.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export class IndexedDbQueryCache implements QueryCache {
164164
const queryData = this.serializer.fromDbTarget(value);
165165
if (
166166
queryData.sequenceNumber <= upperBound &&
167-
activeTargetIds[queryData.targetId] === undefined
167+
activeTargetIds.get(queryData.targetId) === null
168168
) {
169169
count++;
170170
promises.push(this.removeQueryData(txn, queryData));

packages/firestore/src/local/indexeddb_remote_document_cache.ts

+19-6
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ import {
2929
} from '../model/collections';
3030
import { Document, MaybeDocument, NoDocument } from '../model/document';
3131
import { DocumentKey } from '../model/document_key';
32+
import { ResourcePath } from '../model/path';
33+
import { primitiveComparator } from '../util/misc';
3234
import { SortedMap } from '../util/sorted_map';
35+
import { SortedSet } from '../util/sorted_set';
3336

3437
import { SnapshotVersion } from '../core/snapshot_version';
3538
import { assert, fail } from '../util/assert';
@@ -71,12 +74,7 @@ export class IndexedDbRemoteDocumentCache implements RemoteDocumentCache {
7174
doc: DbRemoteDocument
7275
): PersistencePromise<void> {
7376
const documentStore = remoteDocumentsStore(transaction);
74-
return documentStore.put(dbKey(key), doc).next(() => {
75-
this.indexManager.addToCollectionParentIndex(
76-
transaction,
77-
key.path.popLast()
78-
);
79-
});
77+
return documentStore.put(dbKey(key), doc);
8078
}
8179

8280
/**
@@ -449,6 +447,10 @@ export class IndexedDbRemoteDocumentCache implements RemoteDocumentCache {
449447

450448
let sizeDelta = 0;
451449

450+
let collectionParents = new SortedSet<ResourcePath>((l, r) =>
451+
primitiveComparator(l.canonicalString(), r.canonicalString())
452+
);
453+
452454
this.changes.forEach((key, maybeDocument) => {
453455
const previousSize = this.documentSizes.get(key);
454456
assert(
@@ -464,6 +466,8 @@ export class IndexedDbRemoteDocumentCache implements RemoteDocumentCache {
464466
maybeDocument,
465467
this.readTime
466468
);
469+
collectionParents = collectionParents.add(key.path.popLast());
470+
467471
const size = dbDocumentSize(doc);
468472
sizeDelta += size - previousSize!;
469473
promises.push(this.documentCache.addEntry(transaction, key, doc));
@@ -487,6 +491,15 @@ export class IndexedDbRemoteDocumentCache implements RemoteDocumentCache {
487491
}
488492
});
489493

494+
collectionParents.forEach(parent => {
495+
promises.push(
496+
this.documentCache.indexManager.addToCollectionParentIndex(
497+
transaction,
498+
parent
499+
)
500+
);
501+
});
502+
490503
promises.push(this.documentCache.updateMetadata(transaction, sizeDelta));
491504

492505
return PersistencePromise.waitFor(promises);

0 commit comments

Comments
 (0)