Skip to content

Adding getActiveClients() #586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/firestore/src/local/indexeddb_persistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,19 @@ export class IndexedDbPersistence implements Persistence {
});
}

getActiveClients(): Promise<ClientKey[]> {
const clientKeys: ClientKey[] = [];
return this.simpleDb
.runTransaction('readonly', [DbClientMetadata.store], txn => {
return clientMetadataStore(txn).iterate((key, value) => {
if (this.isWithinMaxAge(value.updateTimeMs)) {
clientKeys.push(value.clientKey);
}
});
})
.then(() => clientKeys);
}

getMutationQueue(user: User): MutationQueue {
return IndexedDbMutationQueue.forUser(user, this.serializer);
}
Expand Down
7 changes: 7 additions & 0 deletions packages/firestore/src/local/memory_persistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ import {
import { PersistencePromise } from './persistence_promise';
import { QueryCache } from './query_cache';
import { RemoteDocumentCache } from './remote_document_cache';
import { ClientKey } from './shared_client_state';
import { AsyncQueue } from '../util/async_queue';
import { AutoId } from '../util/misc';

const LOG_TAG = 'MemoryPersistence';

Expand All @@ -49,6 +51,7 @@ export class MemoryPersistence implements Persistence {
private mutationQueues: { [user: string]: MutationQueue } = {};
private remoteDocumentCache = new MemoryRemoteDocumentCache();
private queryCache = new MemoryQueryCache();
private readonly clientId: ClientKey = AutoId.newId();

private started = false;

Expand All @@ -66,6 +69,10 @@ export class MemoryPersistence implements Persistence {
this.started = false;
}

async getActiveClients(): Promise<ClientKey[]> {
return [this.clientId];
}

setPrimaryStateListener(primaryStateListener: PrimaryStateListener) {
// All clients using memory persistence act as primary.
this.queue.enqueue(() => primaryStateListener(true));
Expand Down
12 changes: 12 additions & 0 deletions packages/firestore/src/local/persistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { PersistenceTransaction } from './persistence';
import { PersistencePromise } from './persistence_promise';
import { QueryCache } from './query_cache';
import { RemoteDocumentCache } from './remote_document_cache';
import { ClientKey } from './shared_client_state';

/**
* Opaque interface representing a persistence transaction.
Expand Down Expand Up @@ -94,9 +95,20 @@ export interface Persistence {
* Registers a listener that gets called when the primary state of the
* instance changes. Upon registering, this listener is invoked immediately
* with the current primary state.
*
* PORTING NOTE: This is only used for Web multi-tab.
*/
setPrimaryStateListener(primaryStateListener: PrimaryStateListener);

/**
* Returns the IDs of the clients that are currently active. If multi-tab
* is not supported, returns an array that only contains the local client's
* ID.
*
* PORTING NOTE: This is only used for Web multi-tab.
*/
getActiveClients(): Promise<ClientKey[]>;

/**
* Returns a MutationQueue representing the persisted mutations for the
* given user.
Expand Down
14 changes: 14 additions & 0 deletions packages/firestore/test/unit/specs/persistence_spec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,20 @@ describeSpec('Persistence:', [], () => {
);
});

specTest('Detects all active clients', ['multi-client'], () => {
return (
client(0)
// While we don't verify the client's visibility in this test, the spec
// test framework requires an explicit action before setting an
// expectation.
.becomeHidden()
.expectNumActiveClients(1)
.client(1)
.becomeVisible()
.expectNumActiveClients(2)
);
});

specTest('Single tab acquires primary lease', ['multi-client'], () => {
// This test simulates primary state handoff between two background tabs.
// With all instances in the background, the first active tab acquires
Expand Down
13 changes: 13 additions & 0 deletions packages/firestore/test/unit/specs/spec_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,14 @@ export class SpecBuilder {
return this;
}

expectNumActiveClients(num: number): SpecBuilder {
this.assertStep('Expectations require previous step');
const currentStep = this.currentStep!;
currentStep.stateExpect = currentStep.stateExpect || {};
currentStep.stateExpect.numActiveClients = num;
return this;
}

expectPrimaryState(isPrimary: boolean): SpecBuilder {
this.assertStep('Expectations requires previous step');
const currentStep = this.currentStep!;
Expand Down Expand Up @@ -928,6 +936,11 @@ export class MultiClientSpecBuilder extends SpecBuilder {
return this;
}

expectNumActiveClients(num: number): MultiClientSpecBuilder {
super.expectNumActiveClients(num);
return this;
}

expectPrimaryState(isPrimary: boolean): MultiClientSpecBuilder {
super.expectPrimaryState(isPrimary);
return this;
Expand Down
37 changes: 27 additions & 10 deletions packages/firestore/test/unit/specs/spec_test_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ abstract class TestRunner {
await this.doStep(step);
await this.queue.drain();
this.validateStepExpectations(step.expect!);
this.validateStateExpectations(step.stateExpect!);
await this.validateStateExpectations(step.stateExpect!);
this.eventList = [];
}

Expand Down Expand Up @@ -847,13 +847,19 @@ abstract class TestRunner {
}
}

private validateStateExpectations(expectation: StateExpectation): void {
private async validateStateExpectations(
expectation: StateExpectation
): Promise<void> {
if (expectation) {
if ('numOutstandingWrites' in expectation) {
expect(this.remoteStore.outstandingWrites()).to.equal(
expectation.numOutstandingWrites
);
}
if ('numActiveClients' in expectation) {
const activeClients = await this.persistence.getActiveClients();
expect(activeClients.length).to.equal(expectation.numActiveClients);
}
if ('writeStreamRequestCount' in expectation) {
expect(this.connection.writeStreamRequestCount).to.equal(
expectation.writeStreamRequestCount
Expand Down Expand Up @@ -1147,22 +1153,31 @@ export async function runSpec(
): Promise<void> {
console.log('Running spec: ' + name);
const platform = new TestPlatform(PlatformSupport.getPlatform());

// PORTING NOTE: Non multi-client SDKs only support a single test runner.
let runners: TestRunner[] = [];
for (let i = 0; i < config.numClients; ++i) {
if (usePersistence) {
runners.push(new IndexedDbTestRunner(name, platform, config));
} else {
runners.push(new MemoryTestRunner(name, platform, config));

const ensureRunner = async clientIndex => {
if (!runners[clientIndex]) {
if (usePersistence) {
runners[clientIndex] = new IndexedDbTestRunner(name, platform, config);
} else {
runners[clientIndex] = new MemoryTestRunner(name, platform, config);
}
await runners[clientIndex].start();
}
await runners[i].start();
}
return runners[clientIndex];
};

let lastStep = null;
let count = 0;
try {
await sequence(steps, async step => {
++count;
lastStep = step;
return runners[step.clientIndex || 0].run(step);
return ensureRunner(step.clientIndex || 0).then(runner =>
runner.run(step)
);
});
} catch (err) {
console.warn(
Expand Down Expand Up @@ -1396,6 +1411,8 @@ export interface SpecExpectation {
export interface StateExpectation {
/** Number of outstanding writes in the datastore queue. */
numOutstandingWrites?: number;
/** Number of clients currently marked active. Used in multi-client tests. */
numActiveClients?: number;
/** Number of requests sent to the write stream. */
writeStreamRequestCount?: number;
/** Number of requests sent to the watch stream. */
Expand Down