Skip to content

Commit

Permalink
Support waitForPendingWrites in secondary tabs (#3821)
Browse files Browse the repository at this point in the history
  • Loading branch information
schmidt-sebastian committed Sep 21, 2020
1 parent 5593be6 commit 4dc8817
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/mean-queens-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@firebase/firestore": patch
---

Fixes an issue that prevents `waitForPendingWrites()` from resolving in background tabs when multi-tab is used (https://github.com/firebase/firebase-js-sdk/issues/3816).
1 change: 1 addition & 0 deletions packages/firestore/src/core/sync_engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,7 @@ export async function applyBatchState(
// NOTE: Both these methods are no-ops for batches that originated from
// other clients.
processUserCallback(syncEngineImpl, batchId, error ? error : null);
triggerPendingWritesCallbacks(syncEngineImpl, batchId);
removeCachedMutationBatchMetadata(syncEngineImpl.localStore, batchId);
} else {
fail(`Unknown batchState: ${batchState}`);
Expand Down
15 changes: 15 additions & 0 deletions packages/firestore/test/unit/specs/spec_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,14 @@ export class SpecBuilder {
return this;
}

waitForPendingWrites(): this {
this.nextStep();
this.currentStep = {
waitForPendingWrites: true
};
return this;
}

expectUserCallbacks(docs: {
acknowledged?: string[];
rejected?: string[];
Expand Down Expand Up @@ -944,6 +952,13 @@ export class SpecBuilder {
return this;
}

expectWaitForPendingWritesEvent(count = 1): this {
this.assertStep('Expectations require previous step');
const currentStep = this.currentStep!;
currentStep.expectedWaitForPendingWritesEvents = count;
return this;
}

private static queryToSpec(query: Query): SpecQuery {
// TODO(dimond): full query support
const spec: SpecQuery = { path: query.path.canonicalString() };
Expand Down
38 changes: 37 additions & 1 deletion packages/firestore/test/unit/specs/spec_test_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { SnapshotVersion } from '../../../src/core/snapshot_version';
import {
activeLimboDocumentResolutions,
enqueuedLimboDocumentResolutions,
registerPendingWritesCallback,
SyncEngine,
syncEngineListen,
syncEngineUnlisten,
Expand Down Expand Up @@ -195,6 +196,7 @@ abstract class TestRunner {
private eventList: QueryEvent[] = [];
private acknowledgedDocs: string[];
private rejectedDocs: string[];
private waitForPendingWritesEvents = 0;
private snapshotsInSyncListeners: Array<Observer<void>>;
private snapshotsInSyncEvents = 0;

Expand Down Expand Up @@ -342,6 +344,9 @@ abstract class TestRunner {
this.validateExpectedSnapshotEvents(step.expectedSnapshotEvents!);
await this.validateExpectedState(step.expectedState!);
this.validateSnapshotsInSyncEvents(step.expectedSnapshotsInSyncEvents);
this.validateWaitForPendingWritesEvents(
step.expectedWaitForPendingWritesEvents
);
this.eventList = [];
this.rejectedDocs = [];
this.acknowledgedDocs = [];
Expand Down Expand Up @@ -382,6 +387,8 @@ abstract class TestRunner {
return this.doWriteAck(step.writeAck!);
} else if ('failWrite' in step) {
return this.doFailWrite(step.failWrite!);
} else if ('waitForPendingWrites' in step) {
return this.doWaitForPendingWrites();
} else if ('runTimer' in step) {
return this.doRunTimer(step.runTimer!);
} else if ('drainQueue' in step) {
Expand Down Expand Up @@ -716,6 +723,14 @@ abstract class TestRunner {
});
}

private async doWaitForPendingWrites(): Promise<void> {
const deferred = new Deferred();
void deferred.promise.then(() => ++this.waitForPendingWritesEvents);
return this.queue.enqueue(() =>
registerPendingWritesCallback(this.syncEngine, deferred)
);
}

private async doRunTimer(timer: string): Promise<void> {
// We assume the timer string is a valid TimerID enum value, but if it's
// not, then there won't be a matching item on the queue and
Expand Down Expand Up @@ -912,10 +927,23 @@ abstract class TestRunner {
}
}

private validateWaitForPendingWritesEvents(
expectedCount: number | undefined
): void {
expect(this.waitForPendingWritesEvents).to.eq(
expectedCount || 0,
'for waitForPendingWritesEvents'
);
this.waitForPendingWritesEvents = 0;
}

private validateSnapshotsInSyncEvents(
expectedCount: number | undefined
): void {
expect(this.snapshotsInSyncEvents).to.eq(expectedCount || 0);
expect(this.snapshotsInSyncEvents).to.eq(
expectedCount || 0,
'for snapshotsInSyncEvents'
);
this.snapshotsInSyncEvents = 0;
}

Expand Down Expand Up @@ -1334,6 +1362,8 @@ export interface SpecStep {
writeAck?: SpecWriteAck;
/** Fail a write */
failWrite?: SpecWriteFailure;
/** Add a new `waitForPendingWrites` listener. */
waitForPendingWrites?: true;

/** Fails the listed database actions. */
failDatabase?: false | PersistenceAction[];
Expand Down Expand Up @@ -1387,6 +1417,12 @@ export interface SpecStep {
* If not provided, the test will fail if the step causes events to be raised.
*/
expectedSnapshotsInSyncEvents?: number;

/**
* Optional expected number of waitForPendingWrite callbacks to be called.
* If not provided, the test will fail if the step causes events to be raised.
*/
expectedWaitForPendingWritesEvents?: number;
}

/** [<target-id>, <query-path>] */
Expand Down
113 changes: 113 additions & 0 deletions packages/firestore/test/unit/specs/write_spec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1490,4 +1490,117 @@ describeSpec('Writes:', [], () => {
.expectEvents(query1, { added: [docA, docB], fromCache: true });
}
);

specTest(
'Wait for pending writes resolves after write acknowledgment',
[],
() => {
return spec()
.userSets('collection/a', { k: 'a' })
.userSets('collection/b', { k: 'b' })
.waitForPendingWrites()
.writeAcks('collection/a', 1001)
.failWrite(
'collection/b',
new RpcError(Code.FAILED_PRECONDITION, 'Write error')
)
.expectWaitForPendingWritesEvent();
}
);

specTest('Wait for pending writes resolves with no writes', [], () => {
return spec().waitForPendingWrites().expectWaitForPendingWritesEvent();
});

specTest('Wait for pending writes resolves multiple times', [], () => {
return spec()
.userSets('collection/a', { k: 'a' })
.waitForPendingWrites()
.waitForPendingWrites()
.writeAcks('collection/a', 1001)
.expectWaitForPendingWritesEvent(2);
});

specTest(
'Wait for pending writes resolves if another write is issued',
[],
() => {
return spec()
.userSets('collection/a', { k: 'a' })
.waitForPendingWrites()
.userSets('collection/b', { k: 'b' })
.writeAcks('collection/a', 1001)
.expectWaitForPendingWritesEvent()
.writeAcks('collection/b', 1002);
}
);

specTest(
'Wait for pending writes waits after restart',
['durable-persistence'],
() => {
return spec()
.userSets('collection/a', { k: 'a' })
.restart()
.waitForPendingWrites()
.writeAcks('collection/a', 1001, { expectUserCallback: false })
.expectWaitForPendingWritesEvent();
}
);

specTest(
'Wait for pending writes resolves for write in secondary tab',
['multi-client'],
() => {
return client(0)
.expectPrimaryState(true)
.client(1)
.userSets('collection/a', { k: 'a' })
.waitForPendingWrites()
.client(0)
.writeAcks('collection/a', 1001, { expectUserCallback: false })
.client(1)
.expectUserCallbacks({ acknowledged: ['collection/a'] })
.expectWaitForPendingWritesEvent();
}
);

specTest(
'Wait for pending writes resolves independently for different tabs',
['multi-client'],
() => {
return client(0)
.userSets('collection/a', { k: 'a' })
.waitForPendingWrites()
.client(1)
.userSets('collection/b', { k: 'b' })
.waitForPendingWrites()
.client(2)
.userSets('collection/c', { k: 'c' })
.waitForPendingWrites()
.client(0)
.writeAcks('collection/a', 1001)
.expectWaitForPendingWritesEvent(/* count= */ 1)
.client(1)
.expectWaitForPendingWritesEvent(/* count= */ 0)
.client(2)
.expectWaitForPendingWritesEvent(/* count= */ 0)
.client(0)
.writeAcks('collection/b', 1002, { expectUserCallback: false })
.expectWaitForPendingWritesEvent(/* count= */ 0)
.client(1)
.expectUserCallbacks({ acknowledged: ['collection/b'] })
.expectWaitForPendingWritesEvent(/* count= */ 1)
.client(2)
.expectWaitForPendingWritesEvent(/* count= */ 0)
.client(0)
.writeAcks('collection/c', 1003, { expectUserCallback: false })
.expectWaitForPendingWritesEvent(/* count= */ 0)
.client(1)
.expectWaitForPendingWritesEvent(/* count= */ 0)
.client(2)
.expectUserCallbacks({ acknowledged: ['collection/c'] })
.expectWaitForPendingWritesEvent(/* count= */ 1);
}
);
});

0 comments on commit 4dc8817

Please sign in to comment.