Skip to content

Commit bc2ab66

Browse files
author
Michael Lehenbauer
committed
Fixes #1436: Ensure only the primary tab tries to garbage collect.
Only schedule garbage collections on the primary tab and gracefully ignore if they fail.
1 parent 54df997 commit bc2ab66

File tree

5 files changed

+49
-50
lines changed

5 files changed

+49
-50
lines changed

packages/firestore/src/core/firestore_client.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,6 @@ export class FirestoreClient {
393393
this.asyncQueue,
394394
this.localStore
395395
);
396-
this.lruScheduler.start();
397396
}
398397
const serializer = this.platform.newSerializer(
399398
this.databaseInfo.databaseId
@@ -444,9 +443,16 @@ export class FirestoreClient {
444443

445444
// NOTE: This will immediately call the listener, so we make sure to
446445
// set it after localStore / remoteStore are started.
447-
await this.persistence.setPrimaryStateListener(isPrimary =>
448-
this.syncEngine.applyPrimaryState(isPrimary)
449-
);
446+
await this.persistence.setPrimaryStateListener(async isPrimary => {
447+
await this.syncEngine.applyPrimaryState(isPrimary);
448+
if (this.lruScheduler) {
449+
if (isPrimary && !this.lruScheduler.started) {
450+
this.lruScheduler.start();
451+
} else if (!isPrimary) {
452+
this.lruScheduler.stop();
453+
}
454+
}
455+
});
450456
});
451457
}
452458

packages/firestore/src/core/sync_engine.ts

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import { Deferred } from '../util/promise';
4040
import { SortedMap } from '../util/sorted_map';
4141
import { isNullOrUndefined } from '../util/types';
4242

43-
import { isPrimaryLeaseLostError } from '../local/indexeddb_persistence';
43+
import { ignoreIfPrimaryLeaseLoss } from '../local/indexeddb_persistence';
4444
import { isDocumentChangeMissingError } from '../local/indexeddb_remote_document_cache';
4545
import { ClientId, SharedClientState } from '../local/shared_client_state';
4646
import {
@@ -324,7 +324,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
324324
this.remoteStore.unlisten(queryView.targetId);
325325
this.removeAndCleanupQuery(queryView);
326326
})
327-
.catch(err => this.ignoreIfPrimaryLeaseLoss(err));
327+
.catch(ignoreIfPrimaryLeaseLoss);
328328
}
329329
} else {
330330
this.removeAndCleanupQuery(queryView);
@@ -464,7 +464,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
464464
);
465465
return this.emitNewSnapsAndNotifyLocalStore(changes, remoteEvent);
466466
})
467-
.catch(err => this.ignoreIfPrimaryLeaseLoss(err));
467+
.catch(ignoreIfPrimaryLeaseLoss);
468468
}
469469

470470
/**
@@ -547,7 +547,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
547547
await this.localStore
548548
.releaseQuery(queryView.query, /* keepPersistedQueryData */ false)
549549
.then(() => this.removeAndCleanupQuery(queryView))
550-
.catch(err => this.ignoreIfPrimaryLeaseLoss(err));
550+
.catch(ignoreIfPrimaryLeaseLoss);
551551
this.syncEngineListener!.onWatchError(queryView.query, err);
552552
}
553553
}
@@ -609,7 +609,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
609609
this.sharedClientState.updateMutationState(batchId, 'acknowledged');
610610
return this.emitNewSnapsAndNotifyLocalStore(changes);
611611
})
612-
.catch(err => this.ignoreIfPrimaryLeaseLoss(err));
612+
.catch(ignoreIfPrimaryLeaseLoss);
613613
}
614614

615615
rejectFailedWrite(batchId: BatchId, error: FirestoreError): Promise<void> {
@@ -627,7 +627,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
627627
this.sharedClientState.updateMutationState(batchId, 'rejected', error);
628628
return this.emitNewSnapsAndNotifyLocalStore(changes);
629629
})
630-
.catch(err => this.ignoreIfPrimaryLeaseLoss(err));
630+
.catch(ignoreIfPrimaryLeaseLoss);
631631
}
632632

633633
private addMutationCallback(
@@ -815,24 +815,6 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
815815
await this.localStore.notifyLocalViewChanges(docChangesInAllViews);
816816
}
817817

818-
/**
819-
* Verifies the error thrown by an LocalStore operation. If a LocalStore
820-
* operation fails because the primary lease has been taken by another client,
821-
* we ignore the error (the persistence layer will immediately call
822-
* `applyPrimaryLease` to propagate the primary state change). All other
823-
* errors are re-thrown.
824-
*
825-
* @param err An error returned by a LocalStore operation.
826-
* @return A Promise that resolves after we recovered, or the original error.
827-
*/
828-
private async ignoreIfPrimaryLeaseLoss(err: FirestoreError): Promise<void> {
829-
if (isPrimaryLeaseLostError(err)) {
830-
log.debug(LOG_TAG, 'Unexpectedly lost primary lease');
831-
} else {
832-
throw err;
833-
}
834-
}
835-
836818
private assertSubscribed(fnName: string): void {
837819
assert(
838820
this.syncEngineListener !== null,
@@ -1068,7 +1050,7 @@ export class SyncEngine implements RemoteSyncer, SharedClientStateSyncer {
10681050
this.remoteStore.unlisten(targetId);
10691051
this.removeAndCleanupQuery(queryView);
10701052
})
1071-
.catch(err => this.ignoreIfPrimaryLeaseLoss(err));
1053+
.catch(ignoreIfPrimaryLeaseLoss);
10721054
}
10731055
}
10741056
}

packages/firestore/src/local/indexeddb_persistence.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1045,12 +1045,33 @@ export class IndexedDbPersistence implements Persistence {
10451045
}
10461046
}
10471047

1048-
export function isPrimaryLeaseLostError(err: FirestoreError): boolean {
1048+
function isPrimaryLeaseLostError(err: FirestoreError): boolean {
10491049
return (
10501050
err.code === Code.FAILED_PRECONDITION &&
10511051
err.message === PRIMARY_LEASE_LOST_ERROR_MSG
10521052
);
10531053
}
1054+
1055+
/**
1056+
* Verifies the error thrown by a LocalStore operation. If a LocalStore
1057+
* operation fails because the primary lease has been taken by another client,
1058+
* we ignore the error (the persistence layer will immediately call
1059+
* `applyPrimaryLease` to propagate the primary state change). All other errors
1060+
* are re-thrown.
1061+
*
1062+
* @param err An error returned by a LocalStore operation.
1063+
* @return A Promise that resolves after we recovered, or the original error.
1064+
*/
1065+
export async function ignoreIfPrimaryLeaseLoss(
1066+
err: FirestoreError
1067+
): Promise<void> {
1068+
if (isPrimaryLeaseLostError(err)) {
1069+
log.debug(LOG_TAG, 'Unexpectedly lost primary lease');
1070+
} else {
1071+
throw err;
1072+
}
1073+
}
1074+
10541075
/**
10551076
* Helper to get a typed SimpleDbStore for the primary client object store.
10561077
*/

packages/firestore/src/local/lru_garbage_collector.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import * as log from '../util/log';
2323
import { AnyJs, primitiveComparator } from '../util/misc';
2424
import { CancelablePromise } from '../util/promise';
2525
import { SortedSet } from '../util/sorted_set';
26+
import { ignoreIfPrimaryLeaseLoss } from './indexeddb_persistence';
2627
import { LocalStore } from './local_store';
2728
import { PersistenceTransaction } from './persistence';
2829
import { PersistencePromise } from './persistence_promise';
@@ -247,6 +248,10 @@ export class LruScheduler {
247248
}
248249
}
249250

251+
get started(): boolean {
252+
return this.gcTask !== null;
253+
}
254+
250255
private scheduleGC(): void {
251256
assert(this.gcTask === null, 'Cannot schedule GC while a task is pending');
252257
const delay = this.hasRun ? REGULAR_GC_DELAY_MS : INITIAL_GC_DELAY_MS;
@@ -262,7 +267,8 @@ export class LruScheduler {
262267
this.hasRun = true;
263268
return this.localStore
264269
.collectGarbage(this.garbageCollector)
265-
.then(() => this.scheduleGC());
270+
.then(() => this.scheduleGC())
271+
.catch(ignoreIfPrimaryLeaseLoss);
266272
}
267273
);
268274
}

packages/firestore/src/remote/remote_store.ts

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { Code, FirestoreError } from '../util/error';
3131
import * as log from '../util/log';
3232
import * as objUtils from '../util/obj';
3333

34-
import { isPrimaryLeaseLostError } from '../local/indexeddb_persistence';
34+
import { ignoreIfPrimaryLeaseLoss } from '../local/indexeddb_persistence';
3535
import { DocumentKeySet } from '../model/collections';
3636
import { AsyncQueue } from '../util/async_queue';
3737
import { Datastore } from './datastore';
@@ -564,23 +564,7 @@ export class RemoteStore implements TargetMetadataProvider {
564564
this.writeStream.writeMutations(batch.mutations);
565565
}
566566
})
567-
.catch(err => this.ignoreIfPrimaryLeaseLoss(err));
568-
}
569-
570-
/**
571-
* Verifies the error thrown by an LocalStore operation. If a LocalStore
572-
* operation fails because the primary lease has been taken by another client,
573-
* we ignore the error. All other errors are re-thrown.
574-
*
575-
* @param err An error returned by a LocalStore operation.
576-
* @return A Promise that resolves after we recovered, or the original error.
577-
*/
578-
private ignoreIfPrimaryLeaseLoss(err: FirestoreError): void {
579-
if (isPrimaryLeaseLostError(err)) {
580-
log.debug(LOG_TAG, 'Unexpectedly lost primary lease');
581-
} else {
582-
throw err;
583-
}
567+
.catch(ignoreIfPrimaryLeaseLoss);
584568
}
585569

586570
private onMutationResult(
@@ -656,7 +640,7 @@ export class RemoteStore implements TargetMetadataProvider {
656640

657641
return this.localStore
658642
.setLastStreamToken(emptyByteString())
659-
.catch(err => this.ignoreIfPrimaryLeaseLoss(err));
643+
.catch(ignoreIfPrimaryLeaseLoss);
660644
} else {
661645
// Some other error, don't reset stream token. Our stream logic will
662646
// just retry with exponential backoff.

0 commit comments

Comments
 (0)