Skip to content

Commit 35753cd

Browse files
[Multi-Tab] Merge Enable Persistence Started (#1054)
1 parent 674882f commit 35753cd

File tree

5 files changed

+68
-70
lines changed

5 files changed

+68
-70
lines changed

packages/firestore/src/local/indexeddb_persistence.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export class IndexedDbPersistence implements Persistence {
125125
private readonly window: Window;
126126

127127
private simpleDb: SimpleDb;
128-
private started: boolean;
128+
private _started = false;
129129
private isPrimary = false;
130130
private networkEnabled = true;
131131
private dbName: string;
@@ -184,7 +184,6 @@ export class IndexedDbPersistence implements Persistence {
184184

185185
assert(!this.started, 'IndexedDbPersistence double-started!');
186186
this.allowTabSynchronization = !!synchronizeTabs;
187-
this.started = true;
188187

189188
assert(this.window !== null, "Expected 'window' to be defined");
190189

@@ -198,6 +197,9 @@ export class IndexedDbPersistence implements Persistence {
198197
return this.updateClientMetadataAndTryBecomePrimary().then(() =>
199198
this.scheduleClientMetadataAndPrimaryLeaseRefreshes()
200199
);
200+
})
201+
.then(() => {
202+
this._started = true;
201203
});
202204
}
203205

@@ -391,10 +393,9 @@ export class IndexedDbPersistence implements Persistence {
391393
}
392394

393395
async shutdown(deleteData?: boolean): Promise<void> {
394-
if (!this.started) {
395-
return Promise.resolve();
396-
}
397-
this.started = false;
396+
// The shutdown() operations are idempotent and can be called even when
397+
// start() aborted (e.g. because it couldn't acquire the persistence lease).
398+
this._started = false;
398399

399400
this.markClientZombied();
400401
if (this.clientMetadataRefresher) {
@@ -434,6 +435,10 @@ export class IndexedDbPersistence implements Persistence {
434435
.then(() => clientIds);
435436
}
436437

438+
get started(): boolean {
439+
return this._started;
440+
}
441+
437442
getMutationQueue(user: User): MutationQueue {
438443
return IndexedDbMutationQueue.forUser(user, this.serializer);
439444
}

packages/firestore/src/local/local_store.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ export class LocalStore {
176176
*/
177177
private sharedClientState: SharedClientState
178178
) {
179+
assert(
180+
persistence.started,
181+
'LocalStore was passed an unstarted persistence implementation'
182+
);
179183
this.mutationQueue = persistence.getMutationQueue(initialUser);
180184
this.remoteDocuments = persistence.getRemoteDocumentCache();
181185
this.queryCache = persistence.getQueryCache();

packages/firestore/src/local/memory_persistence.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,23 @@ export class MemoryPersistence implements Persistence {
5050
private remoteDocumentCache = new MemoryRemoteDocumentCache();
5151
private queryCache = new MemoryQueryCache();
5252

53-
private started = false;
53+
private _started = false;
5454

5555
constructor(private readonly clientId: ClientId) {}
5656

5757
async start(): Promise<void> {
5858
// No durable state to read on startup.
59-
assert(!this.started, 'MemoryPersistence double-started!');
60-
this.started = true;
59+
assert(!this._started, 'MemoryPersistence double-started!');
60+
this._started = true;
6161
}
6262

6363
async shutdown(deleteData?: boolean): Promise<void> {
6464
// No durable state to ensure is closed on shutdown.
65-
assert(this.started, 'MemoryPersistence shutdown without start!');
66-
this.started = false;
65+
this._started = false;
66+
}
67+
68+
get started(): boolean {
69+
return this._started;
6770
}
6871

6972
async getActiveClients(): Promise<ClientId[]> {

packages/firestore/src/local/persistence.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ export type PrimaryStateListener = (isPrimary: boolean) => Promise<void>;
8484
// point out (and maybe enforce) when methods cannot safely be used from
8585
// secondary tabs.
8686
export interface Persistence {
87+
/**
88+
* Whether or not this persistence instance has been started.
89+
*/
90+
readonly started: boolean;
91+
8792
/**
8893
* Starts persistent storage, opening the database or similar.
8994
*

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

Lines changed: 40 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,6 @@ class MockConnection implements Connection {
141141
/** A Deferred that is resolved once watch opens. */
142142
watchOpen = new Deferred<void>();
143143

144-
reset(): void {
145-
this.watchStreamRequestCount = 0;
146-
this.writeStreamRequestCount = 0;
147-
this.earlyWrites = [];
148-
this.activeTargets = [];
149-
}
150-
151144
invokeRPC<Req>(rpcName: string, request: Req): never {
152145
throw new Error('Not implemented!');
153146
}
@@ -399,23 +392,29 @@ abstract class TestRunner {
399392
'host',
400393
false
401394
);
395+
396+
// TODO(mrschmidt): During client startup in `firestore_client`, we block
397+
// the AsyncQueue from executing any operation. We should mimic this in the
398+
// setup of the spec tests.
402399
this.queue = new AsyncQueue();
403400
this.serializer = new JsonProtoSerializer(this.databaseInfo.databaseId, {
404401
useProto3Json: true
405402
});
406-
this.persistence = this.getPersistence(this.serializer);
407403

408404
this.useGarbageCollection = config.useGarbageCollection;
409405

410-
this.init();
411-
412406
this.expectedLimboDocs = [];
413407
this.expectedActiveTargets = {};
414408
this.acknowledgedDocs = [];
415409
this.rejectedDocs = [];
416410
}
417411

418-
private init(): void {
412+
async start(): Promise<void> {
413+
this.persistence = await this.initPersistence(this.serializer);
414+
await this.init();
415+
}
416+
417+
private async init(): Promise<void> {
419418
const garbageCollector = this.getGarbageCollector();
420419

421420
this.sharedClientState = this.getSharedClientState();
@@ -466,6 +465,16 @@ abstract class TestRunner {
466465
this.sharedClientState.onlineStateHandler = sharedClientStateOnlineStateChangedHandler;
467466

468467
this.eventManager = new EventManager(this.syncEngine);
468+
469+
await this.localStore.start();
470+
await this.sharedClientState.start();
471+
await this.remoteStore.start();
472+
473+
await this.persistence.setPrimaryStateListener(isPrimary =>
474+
this.syncEngine.applyPrimaryState(isPrimary)
475+
);
476+
477+
this.started = true;
469478
}
470479

471480
private getGarbageCollector(): GarbageCollector {
@@ -474,32 +483,16 @@ abstract class TestRunner {
474483
: new NoOpGarbageCollector();
475484
}
476485

477-
protected abstract getPersistence(
486+
protected abstract initPersistence(
478487
serializer: JsonProtoSerializer
479-
): Persistence;
480-
481-
protected abstract startPersistence(persistence: Persistence): Promise<void>;
488+
): Promise<Persistence>;
482489

483490
protected abstract getSharedClientState(): SharedClientState;
484491

485492
get isPrimaryClient(): boolean {
486493
return this.syncEngine.isPrimaryClient;
487494
}
488495

489-
async start(): Promise<void> {
490-
this.connection.reset();
491-
await this.startPersistence(this.persistence);
492-
await this.localStore.start();
493-
await this.sharedClientState.start();
494-
await this.remoteStore.start();
495-
496-
await this.persistence.setPrimaryStateListener(isPrimary =>
497-
this.syncEngine.applyPrimaryState(isPrimary)
498-
);
499-
500-
this.started = true;
501-
}
502-
503496
async shutdown(): Promise<void> {
504497
if (this.started) {
505498
await this.doShutdown();
@@ -874,19 +867,9 @@ abstract class TestRunner {
874867
// No local store to shutdown.
875868
await this.remoteStore.shutdown();
876869

877-
this.init();
878-
879870
// We have to schedule the starts, otherwise we could end up with
880871
// interleaved events.
881-
await this.queue.enqueue(async () => {
882-
await this.localStore.start();
883-
await this.remoteStore.start();
884-
await this.sharedClientState.start();
885-
886-
await this.persistence.setPrimaryStateListener(isPrimary =>
887-
this.syncEngine.applyPrimaryState(isPrimary)
888-
);
889-
});
872+
await this.queue.enqueue(() => this.init());
890873
}
891874

892875
private async doApplyClientState(state: SpecClientState): Promise<void> {
@@ -1142,16 +1125,16 @@ abstract class TestRunner {
11421125
}
11431126

11441127
class MemoryTestRunner extends TestRunner {
1145-
protected getPersistence(serializer: JsonProtoSerializer): Persistence {
1146-
return new MemoryPersistence(this.clientId);
1147-
}
1148-
11491128
protected getSharedClientState(): SharedClientState {
11501129
return new MemorySharedClientState();
11511130
}
11521131

1153-
protected startPersistence(persistence: Persistence): Promise<void> {
1154-
return persistence.start();
1132+
protected async initPersistence(
1133+
serializer: JsonProtoSerializer
1134+
): Promise<Persistence> {
1135+
const persistence = new MemoryPersistence(this.clientId);
1136+
await persistence.start();
1137+
return persistence;
11551138
}
11561139
}
11571140

@@ -1162,16 +1145,6 @@ class MemoryTestRunner extends TestRunner {
11621145
class IndexedDbTestRunner extends TestRunner {
11631146
static TEST_DB_NAME = 'firestore/[DEFAULT]/specs';
11641147

1165-
protected getPersistence(serializer: JsonProtoSerializer): Persistence {
1166-
return new IndexedDbPersistence(
1167-
IndexedDbTestRunner.TEST_DB_NAME,
1168-
this.clientId,
1169-
this.platform,
1170-
this.queue,
1171-
serializer
1172-
);
1173-
}
1174-
11751148
protected getSharedClientState(): SharedClientState {
11761149
return new WebStorageSharedClientState(
11771150
this.queue,
@@ -1182,10 +1155,18 @@ class IndexedDbTestRunner extends TestRunner {
11821155
);
11831156
}
11841157

1185-
protected startPersistence(persistence: Persistence): Promise<void> {
1186-
return (persistence as IndexedDbPersistence).start(
1187-
/*synchronizeTabs=*/ true
1158+
protected async initPersistence(
1159+
serializer: JsonProtoSerializer
1160+
): Promise<Persistence> {
1161+
const persistence = new IndexedDbPersistence(
1162+
IndexedDbTestRunner.TEST_DB_NAME,
1163+
this.clientId,
1164+
this.platform,
1165+
this.queue,
1166+
serializer
11881167
);
1168+
await persistence.start(/*synchronizeTabs=*/ true);
1169+
return persistence;
11891170
}
11901171

11911172
static destroyPersistence(): Promise<void> {

0 commit comments

Comments
 (0)