-
Notifications
You must be signed in to change notification settings - Fork 897
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
Closing the write and the watch stream after 60s of idleness #275
Conversation
b744b9d
to
a8a7d76
Compare
@@ -23,6 +23,9 @@ import firebase from './firebase_export'; | |||
import { EmptyCredentialsProvider } from '../../../src/api/credentials'; | |||
import { PlatformSupport } from '../../../src/platform/platform'; | |||
import { AsyncQueue } from '../../../src/util/async_queue'; | |||
import { Firestore, FirestoreDatabase } from '../../../src/api/database'; | |||
import { FirebaseApp } from '../../../../app/index'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will break, if you are trying to reference other modules you need to do it via the module names
i.e.
import { FirebaseApp } from '@firebase/app';
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Thanks for debugging this!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly LGTM
@@ -455,6 +455,11 @@ export class Firestore implements firestore.Firestore, FirebaseService { | |||
); | |||
} | |||
} | |||
|
|||
/** Creates a new AsyncQueue. Can be overwritten to provide a custom queue. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/overwritten/overridden/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
* or null if empty. | ||
* @param afterBatchId If provided, the batch to search after. | ||
* @returns The next mutation or null if there wasn't one. | ||
* or null if empty. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for fixing these things up.
return this.handleIdleCloseTimer(); | ||
}, IDLE_TIMEOUT_MS) | ||
.catch((err: FirestoreError) => { | ||
// We ignore Promise rejections for cancelled idle checks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment doesn't line up with the actual behavior. We don't ignore the rejection: we assert that it can only be of one type. A better comment here would articulate why we're able to make this assertion, perhaps because this can't fail except via the timeout and that's guaranteed to have Code.CANCELLED
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I added this is that running the tests with this spewed a bunch of warnings in Chrome about unrejected Promises that were not handled. I made this comment better.
/** | ||
* Closes the stream and cleans up as necessary: | ||
* | ||
* <ul> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Typescript comments aren't thinly veiled HTML the way Javadoc comments are. Do this as a markdown list, like we did in mutation.ts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adjusted. Thanks for pointing this out.
error?: FirestoreError | ||
): Promise<void> { | ||
assert( | ||
finalState == PersistentStreamState.Error || isNullOrUndefined(error), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As in the iOS PR, this allows the error to be nil if the finalState is FSTStreamStateError. Is that desirable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This gets invoked by 'handleStreamClose' (the GRPC callback handler) and making this stricter would require that the server only ever closes a stream with an error.
const deferred = new Deferred<T>(); | ||
setTimeout(() => { | ||
let handle = window.setTimeout(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why window.setTimeout()
instead of the global? This is incompatible with node without extra hacks to define window.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setTimeout()
is defined to (perhaps incorrectly) return a Timer. See microsoft/TypeScript#842 (comment)
const deferred = new Deferred<T>(); | ||
setTimeout(() => { | ||
let handle = window.setTimeout(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, this can be const handle
instead of let handle
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
this.delayedOperations.forEach((deferred, handle) => { | ||
window.clearTimeout(handle); | ||
deferred.reject( | ||
new FirestoreError(Code.CANCELLED, 'Cancelled during queue drain') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These failures are potentially user visible so we need a better message about what can cause these to happen and how to avoid them if that's a problem.
If draining only happens during shutdown, the message can reflect that: 'Operation cancelled by shutdown'
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only used in tests, but I could see that we may want to call this during shutdown as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM mod resolution of travis failures
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove the package-lock.json
we have the top level yarn.lock
that serves the same purpose.
cf8f359
to
15c781c
Compare
@wilhuff I fixed the failing integration tests. The types we referred to in the tests ended up being minified, while the Public API that we checked against was not. I migrated the test to use the internal representation of the Public API. You can see a test run here: |
fa311b9
to
49ae902
Compare
- @firebase/[email protected] - @firebase/[email protected] - @firebase/[email protected] - [email protected] - @firebase/[email protected] - @firebase/[email protected] - @firebase/[email protected] - @firebase/[email protected] - @firebase/[email protected]
…ment (#279) * Adds a demo app to help test and facilitate Firebase Auth API development.
Fixes verifyAssertion unrecoverable errors when returnIdpCredential is set to true. In this case, the error code is returned along with the credential in the errorMessage without any ID token/refresh token. Catch, suppress/handle when localStorage is null or when `localStorage.getItem` throws a security error due to access being disabled by the browser for whatever reason.
46dd4a5
to
3e3f205
Compare
So there's good news and bad news. 👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there. 😕 The bad news is that it appears that one or more commits were authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that here in the pull request. Note to project maintainer: This is a terminal state, meaning the |
@mikelehen I addressed your feedback. The drain() method in the AsyncQueue now optionally allows for immediate execution of delayed tasks. Furthermore, I have exposed this queue via firestore.INTERNAL. The tests can now access the public Firestore client and no longer need to rely on an internal client that exposes the queue. |
b209f80
to
84c6938
Compare
// may also contain entries that have already been run (in which case `handle` is | ||
// null). | ||
// | ||
// tslint:disable-next-line:no-any Accept any type of delayed operation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use AnyJs instead of "any" ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
this.delayedOpCount--; // decrement once it's actually queued. | ||
this.delayedOperationsCount--; | ||
if (this.delayedOperationsCount > 0) { | ||
delayedOp.handle = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit but I'm not a fan of this sort of optimization. It saves an assignment in some cases, but reads much less naturally (to me at least). I'd just do:
delayedOp.handle = null;
if (this.delayedOperationsCount === 0) {
this.delayedOperations = [];
}
If you insist on optimizing this sort of thing, you should go ahead and optimize away the decrement too!
if (this.delayedOperationsCount !== 1) {
this.delayedOperationsCount--;
delayedOp.handle = null;
} else {
this.delayedOperations = [];
}
(please don't actually do that :-))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This wasn't meant to be the newest and greatest performance optimization. Rather, it just felt naturally to do this at the time. Your version is a bit more concise, so I went with that. Thanks.
const deferred = new Deferred<T>(); | ||
setTimeout(() => { | ||
this.delayedOperationsCount++; | ||
const nextIndex = this.delayedOperations.length; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can move this down and do:
const delayedOpIndex = this.delayedOperations.push(delayedOp);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't actually need the index anymore and removed it altogether.
return this.schedule(() => Promise.resolve(undefined)); | ||
/** | ||
* Waits until all currently scheduled tasks are finished executing. Tasks | ||
* schedule with a delay can be rejected or queued for immediate execution. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
schedule => scheduled
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -374,7 +375,8 @@ export class Firestore implements firestore.Firestore, FirebaseService { | |||
}, | |||
// Exposed via INTERNAL for use in tests. | |||
disableNetwork: () => this._firestoreClient.disableNetwork(), | |||
enableNetwork: () => this._firestoreClient.enableNetwork() | |||
enableNetwork: () => this._firestoreClient.enableNetwork(), | |||
queue: this._queue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a big fan of exposing the whole queue externally since:
- Although not documented or supported, this is now exposed to consumers of the SDK and could be abused.
- The only reason this works is because our minification sucks right now and we only minify things that begin or end with
_
. Really, the entire AsyncQueue class should be minified and all of the methods should have obfuscated names. - In general, I'd rather we make the testable API explicit and minimal. Exposing more than we need to leads to a slippery slope heading towards undesirable tight coupling between tests and implementation.
So I'd recommend exposing drainAsyncQueue() or similar (FWIW if you wanted you could make drain() always execute delayed tasks; I don't think it needs to be optional) and either remove the delayedOperationsCount assertions from tests or have the exposed method return the count of operations that were drained or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went with drainAsyncTasks
. In the integration tests that use Firestore, I can indeed always execute the delayed tasks. I can't do this in the spec tests (since that always ends up closing the stream and confuses the tests).
The stream tests use Datastore directly and I allowed the queue to be passed in. That way, I can verify that I have at least one pending task.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Thanks!
This is a port of firebase/firebase-ios-sdk#388