Skip to content

Only run the provided delayed operation early #3101

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 3 commits into from
May 26, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions packages/firestore/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Unreleased
- [fixed] Fixed an issue that could cause Firestore to temporarily go
offline when a Window visibility event occurred.
- [feature] Added support for calling `FirebaseFiresore.settings` with
`{ ignoreUndefinedProperties: true }`. When set, Firestore ignores
undefined properties inside objects rather than rejecting the API call.
Expand Down
2 changes: 1 addition & 1 deletion packages/firestore/src/local/index_free_query_engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ export class IndexFreeQueryEngine implements QueryEngine {
if (getLogLevel() <= LogLevel.DEBUG) {
logDebug(
'IndexFreeQueryEngine',
'Using full collection scan to execute query: %s',
'Using full collection scan to execute query:',
query.toString()
);
}
Expand Down
14 changes: 6 additions & 8 deletions packages/firestore/src/util/async_queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,23 +463,21 @@ export class AsyncQueue {
}

/**
* For Tests: Runs some or all delayed operations early.
* Runs some or all delayed operations early.
*
* @param lastTimerId Delayed operations up to and including this TimerId will
* be drained. Throws if no such operation exists. Pass TimerId.All to run
* all delayed operations.
* @param timerId Delayed operations to run. Pass TimerId.All to run all
* delayed operations.
* @returns a Promise that resolves once all operations have been run.
*/
runDelayedOperationsEarly(lastTimerId: TimerId): Promise<void> {
runDelayedOperationsEarly(timerId: TimerId): Promise<void> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks the intent of what this test-only method was for. The idea was that you could fast-forward time until the delayed operation ran, including anything scheduled prior to it. In this interface, now you have to know exactly what you want to run, which makes this a much less effective tool for testing. In particular, this makes it really hard to use this for spec tests because we're no longer simulating the actual passage of time, but merely jumping ahead to this specific event. This makes it far less likely that we'll catch scheduling-related problems with tests.

If we really need to allow a scheduled operation to run early, can't we add this as a method to the DelayedOperation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the code to return DelayedOperation instead of CancellablePromise, and added a skipDelay to ExponentialBackoff.

I feel that we might even get rid of the TimerIds with this change, since we now have direct access to the underlying operations - but that of course would remove the ability to run delayed operations in order. It might drastically simplify this code though.

// Note that draining may generate more delayed ops, so we do that first.
return this.drain().then(() => {
// Run ops in the same order they'd run if they ran naturally.
this.delayedOperations.sort((a, b) => a.targetTimeMs - b.targetTimeMs);

for (const op of this.delayedOperations) {
op.skipDelay();
if (lastTimerId !== TimerId.All && op.timerId === lastTimerId) {
break;
if (timerId === TimerId.All || op.timerId === timerId) {
op.skipDelay();
}
}

Expand Down
10 changes: 8 additions & 2 deletions packages/firestore/test/unit/util/async_queue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import * as chaiAsPromised from 'chai-as-promised';
import { expect, use } from 'chai';
import { AsyncQueue, TimerId } from '../../../src/util/async_queue';
import { Code } from '../../../src/util/error';
import { getLogLevel, setLogLevel, LogLevel } from '../../../src/util/log';
import { getLogLevel, LogLevel, setLogLevel } from '../../../src/util/log';
import { Deferred, Rejecter, Resolver } from '../../../src/util/promise';
import { fail } from '../../../src/util/assert';
import { IndexedDbTransactionError } from '../../../src/local/simple_db';
Expand Down Expand Up @@ -206,7 +206,13 @@ describe('AsyncQueue', () => {
queue.enqueueAndForget(() => doStep(2));

await queue.runDelayedOperationsEarly(timerId3);
expect(completedSteps).to.deep.equal([1, 2, 3, 4]);
expect(completedSteps).to.deep.equal([1, 2, 4]);

await queue.runDelayedOperationsEarly(timerId2);
expect(completedSteps).to.deep.equal([1, 2, 4, 3]);

await queue.runDelayedOperationsEarly(timerId1);
expect(completedSteps).to.deep.equal([1, 2, 4, 3, 5]);
});

it('Retries retryable operations', async () => {
Expand Down