Skip to content

Commit 27e0693

Browse files
Merge 8d08444 into 8143c83
2 parents 8143c83 + 8d08444 commit 27e0693

File tree

2 files changed

+142
-62
lines changed

2 files changed

+142
-62
lines changed

packages/firestore/src/local/indexeddb_persistence.ts

+72-50
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ const MAX_PRIMARY_ELIGIBLE_AGE_MS = 5000;
101101
const CLIENT_METADATA_REFRESH_INTERVAL_MS = 4000;
102102
/** User-facing error when the primary lease is required but not available. */
103103
const PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG =
104-
'Another tab has exclusive access to the persistence layer. ' +
104+
'Failed to obtain exclusive access to the persistence layer. ' +
105105
'To allow shared access, make sure to invoke ' +
106106
'`enablePersistence()` with `synchronizeTabs:true` in all tabs.';
107107
const UNSUPPORTED_PLATFORM_ERROR_MSG =
@@ -191,11 +191,12 @@ export class IndexedDbPersistence implements Persistence {
191191
private readonly document: Document | null;
192192
private readonly window: Window;
193193

194-
// Technically these types should be `| undefined` because they are
194+
// Technically `simpleDb` should be `| undefined` because it is
195195
// initialized asynchronously by start(), but that would be more misleading
196196
// than useful.
197197
private simpleDb!: SimpleDb;
198-
private listenSequence!: ListenSequence;
198+
199+
private listenSequence: ListenSequence | null = null;
199200

200201
private _started = false;
201202
private isPrimary = false;
@@ -287,7 +288,15 @@ export class IndexedDbPersistence implements Persistence {
287288
// having the persistence lock), so it's the first thing we should do.
288289
return this.updateClientMetadataAndTryBecomePrimary();
289290
})
290-
.then(() => {
291+
.then(e => {
292+
if (!this.isPrimary && !this.allowTabSynchronization) {
293+
// Fail `start()` if `synchronizeTabs` is disabled and we cannot
294+
// obtain the primary lease.
295+
throw new FirestoreError(
296+
Code.FAILED_PRECONDITION,
297+
PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG
298+
);
299+
}
291300
this.attachVisibilityHandler();
292301
this.attachWindowUnloadHook();
293302

@@ -375,8 +384,10 @@ export class IndexedDbPersistence implements Persistence {
375384
* primary lease.
376385
*/
377386
private updateClientMetadataAndTryBecomePrimary(): Promise<void> {
378-
return this.simpleDb
379-
.runTransaction('readwrite', ALL_STORES, txn => {
387+
return this.runTransaction(
388+
'updateClientMetadataAndTryBecomePrimary',
389+
'readwrite',
390+
txn => {
380391
const metadataStore = clientMetadataStore(txn);
381392
return metadataStore
382393
.put(
@@ -409,10 +420,18 @@ export class IndexedDbPersistence implements Persistence {
409420
return /* canActAsPrimary= */ false;
410421
}
411422
});
412-
})
423+
}
424+
)
413425
.catch(e => {
414426
if (!this.allowTabSynchronization) {
415-
throw e;
427+
if (e.name === 'IndexedDbTransactionError') {
428+
logDebug(LOG_TAG, 'Failed to extend owner lease: ', e);
429+
// Proceed with the existing state. Any subsequent access to
430+
// IndexedDB will verify the lease.
431+
return this.isPrimary;
432+
} else {
433+
throw e;
434+
}
416435
}
417436

418437
logDebug(
@@ -433,7 +452,7 @@ export class IndexedDbPersistence implements Persistence {
433452
}
434453

435454
private verifyPrimaryLease(
436-
txn: SimpleDbTransaction
455+
txn: PersistenceTransaction
437456
): PersistencePromise<boolean> {
438457
const store = primaryClientStore(txn);
439458
return store.get(DbPrimaryClient.key).next(primaryClient => {
@@ -442,7 +461,7 @@ export class IndexedDbPersistence implements Persistence {
442461
}
443462

444463
private removeClientMetadata(
445-
txn: SimpleDbTransaction
464+
txn: PersistenceTransaction
446465
): PersistencePromise<void> {
447466
const metadataStore = clientMetadataStore(txn);
448467
return metadataStore.delete(this.clientId);
@@ -536,7 +555,7 @@ export class IndexedDbPersistence implements Persistence {
536555
* (foreground) client should become leaseholder instead.
537556
*/
538557
private canActAsPrimary(
539-
txn: SimpleDbTransaction
558+
txn: PersistenceTransaction
540559
): PersistencePromise<boolean> {
541560
const store = primaryClientStore(txn);
542561
return store
@@ -645,15 +664,13 @@ export class IndexedDbPersistence implements Persistence {
645664
}
646665
this.detachVisibilityHandler();
647666
this.detachWindowUnloadHook();
648-
await this.simpleDb.runTransaction(
649-
'readwrite',
650-
[DbPrimaryClient.store, DbClientMetadata.store],
651-
txn => {
652-
return this.releasePrimaryLeaseIfHeld(txn).next(() =>
653-
this.removeClientMetadata(txn)
654-
);
655-
}
656-
);
667+
await this.runTransaction('shutdown', 'readwrite', txn => {
668+
return this.releasePrimaryLeaseIfHeld(txn).next(() =>
669+
this.removeClientMetadata(txn)
670+
);
671+
}).catch(e => {
672+
logDebug(LOG_TAG, 'Proceeding with shutdown despite failure: ', e);
673+
});
657674
this.simpleDb.close();
658675

659676
// Remove the entry marking the client as zombied from LocalStorage since
@@ -684,19 +701,15 @@ export class IndexedDbPersistence implements Persistence {
684701
* PORTING NOTE: This is only used for Web multi-tab.
685702
*/
686703
getActiveClients(): Promise<ClientId[]> {
687-
return this.simpleDb.runTransaction(
688-
'readonly',
689-
[DbClientMetadata.store],
690-
txn => {
691-
return clientMetadataStore(txn)
692-
.loadAll()
693-
.next(clients =>
694-
this.filterActiveClients(clients, MAX_CLIENT_AGE_MS).map(
695-
clientMetadata => clientMetadata.clientId
696-
)
697-
);
698-
}
699-
);
704+
return this.runTransaction('getActiveClients', 'readonly', txn => {
705+
return clientMetadataStore(txn)
706+
.loadAll()
707+
.next(clients =>
708+
this.filterActiveClients(clients, MAX_CLIENT_AGE_MS).map(
709+
clientMetadata => clientMetadata.clientId
710+
)
711+
);
712+
});
700713
}
701714

702715
static async clearPersistence(persistenceKey: string): Promise<void> {
@@ -767,7 +780,9 @@ export class IndexedDbPersistence implements Persistence {
767780
.runTransaction(simpleDbMode, ALL_STORES, simpleDbTxn => {
768781
persistenceTransaction = new IndexedDbTransaction(
769782
simpleDbTxn,
770-
this.listenSequence.next()
783+
this.listenSequence
784+
? this.listenSequence.next()
785+
: ListenSequence.INVALID
771786
);
772787

773788
if (mode === 'readwrite-primary') {
@@ -776,12 +791,12 @@ export class IndexedDbPersistence implements Persistence {
776791
// executing transactionOperation(). This ensures that even if the
777792
// transactionOperation takes a long time, we'll use a recent
778793
// leaseTimestampMs in the extended (or newly acquired) lease.
779-
return this.verifyPrimaryLease(simpleDbTxn)
794+
return this.verifyPrimaryLease(persistenceTransaction)
780795
.next(holdsPrimaryLease => {
781796
if (holdsPrimaryLease) {
782797
return /* holdsPrimaryLease= */ true;
783798
}
784-
return this.canActAsPrimary(simpleDbTxn);
799+
return this.canActAsPrimary(persistenceTransaction);
785800
})
786801
.next(holdsPrimaryLease => {
787802
if (!holdsPrimaryLease) {
@@ -800,14 +815,14 @@ export class IndexedDbPersistence implements Persistence {
800815
return transactionOperation(persistenceTransaction);
801816
})
802817
.next(result => {
803-
return this.acquireOrExtendPrimaryLease(simpleDbTxn).next(
804-
() => result
805-
);
818+
return this.acquireOrExtendPrimaryLease(
819+
persistenceTransaction
820+
).next(() => result);
806821
});
807822
} else {
808-
return this.verifyAllowTabSynchronization(simpleDbTxn).next(() =>
809-
transactionOperation(persistenceTransaction)
810-
);
823+
return this.verifyAllowTabSynchronization(
824+
persistenceTransaction
825+
).next(() => transactionOperation(persistenceTransaction));
811826
}
812827
})
813828
.then(result => {
@@ -823,7 +838,7 @@ export class IndexedDbPersistence implements Persistence {
823838
// TODO(b/114226234): Remove this check when `synchronizeTabs` can no longer
824839
// be turned off.
825840
private verifyAllowTabSynchronization(
826-
txn: SimpleDbTransaction
841+
txn: PersistenceTransaction
827842
): PersistencePromise<void> {
828843
const store = primaryClientStore(txn);
829844
return store.get(DbPrimaryClient.key).next(currentPrimary => {
@@ -836,7 +851,10 @@ export class IndexedDbPersistence implements Persistence {
836851
!this.isClientZombied(currentPrimary.ownerId);
837852

838853
if (currentLeaseIsValid && !this.isLocalClient(currentPrimary)) {
839-
if (!currentPrimary!.allowTabSynchronization) {
854+
if (
855+
!this.allowTabSynchronization ||
856+
!currentPrimary!.allowTabSynchronization
857+
) {
840858
throw new FirestoreError(
841859
Code.FAILED_PRECONDITION,
842860
PRIMARY_LEASE_EXCLUSIVE_ERROR_MSG
@@ -851,7 +869,7 @@ export class IndexedDbPersistence implements Persistence {
851869
* method does not verify that the client is eligible for this lease.
852870
*/
853871
private acquireOrExtendPrimaryLease(
854-
txn: SimpleDbTransaction
872+
txn: PersistenceTransaction
855873
): PersistencePromise<void> {
856874
const newPrimary = new DbPrimaryClient(
857875
this.clientId,
@@ -887,7 +905,7 @@ export class IndexedDbPersistence implements Persistence {
887905

888906
/** Checks the primary lease and removes it if we are the current primary. */
889907
private releasePrimaryLeaseIfHeld(
890-
txn: SimpleDbTransaction
908+
txn: PersistenceTransaction
891909
): PersistencePromise<void> {
892910
const store = primaryClientStore(txn);
893911
return store.get(DbPrimaryClient.key).next(primaryClient => {
@@ -1052,18 +1070,22 @@ export class IndexedDbPersistence implements Persistence {
10521070
* Helper to get a typed SimpleDbStore for the primary client object store.
10531071
*/
10541072
function primaryClientStore(
1055-
txn: SimpleDbTransaction
1073+
txn: PersistenceTransaction
10561074
): SimpleDbStore<DbPrimaryClientKey, DbPrimaryClient> {
1057-
return txn.store<DbPrimaryClientKey, DbPrimaryClient>(DbPrimaryClient.store);
1075+
return IndexedDbPersistence.getStore<DbPrimaryClientKey, DbPrimaryClient>(
1076+
txn,
1077+
DbPrimaryClient.store
1078+
);
10581079
}
10591080

10601081
/**
10611082
* Helper to get a typed SimpleDbStore for the client metadata object store.
10621083
*/
10631084
function clientMetadataStore(
1064-
txn: SimpleDbTransaction
1085+
txn: PersistenceTransaction
10651086
): SimpleDbStore<DbClientMetadataKey, DbClientMetadata> {
1066-
return txn.store<DbClientMetadataKey, DbClientMetadata>(
1087+
return IndexedDbPersistence.getStore<DbClientMetadataKey, DbClientMetadata>(
1088+
txn,
10671089
DbClientMetadata.store
10681090
);
10691091
}

0 commit comments

Comments
 (0)