-
Notifications
You must be signed in to change notification settings - Fork 948
Don't crash if Target cannot be persisted #2944
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,16 +20,19 @@ import { EventHandler } from '../util/misc'; | |
import { ObjectMap } from '../util/obj_map'; | ||
import { Query } from './query'; | ||
import { SyncEngine, SyncEngineListener } from './sync_engine'; | ||
import { OnlineState, TargetId } from './types'; | ||
import { DocumentViewChange, ChangeType, ViewSnapshot } from './view_snapshot'; | ||
import { OnlineState } from './types'; | ||
import { ChangeType, DocumentViewChange, ViewSnapshot } from './view_snapshot'; | ||
import { logError } from '../util/log'; | ||
import { Code, FirestoreError } from '../util/error'; | ||
|
||
const LOG_TAG = 'EventManager'; | ||
|
||
/** | ||
* Holds the listeners and the last received ViewSnapshot for a query being | ||
* tracked by EventManager. | ||
*/ | ||
class QueryListenersInfo { | ||
viewSnap: ViewSnapshot | null = null; | ||
targetId: TargetId = 0; | ||
viewSnap: ViewSnapshot | undefined = undefined; | ||
listeners: QueryListener[] = []; | ||
} | ||
|
||
|
@@ -59,16 +62,32 @@ export class EventManager implements SyncEngineListener { | |
this.syncEngine.subscribe(this); | ||
} | ||
|
||
listen(listener: QueryListener): Promise<TargetId> { | ||
async listen(listener: QueryListener): Promise<void> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the return value since a failed target doesn't have an ID. The only consumer of the targetId result were the spec tests, which already verify the target ID in every step as part of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why doesn't failing to listen reject the promise here? If it did then you could return the TargetId in the successful case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I reject the Promise here, then FirestoreClient has to handle the rejected Promise. This is actually pretty nice and keeps the code in EventManager much cleaner. The calling code is also still clean, since it was simple to begin with. The problem I see is that this makes this code non-testable. The spec tests don't use FirestoreClient, and the integration cannot inject failures. I have made the change regardless and added some special handling in SyncEngine. I am not sure if this is an improvement though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see what you mean; the spec test has to emulate this for itself. That's not completely awful but you're probably right that the other way was better because it was testable. Would it help to put the error handling in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think SyncEngine is not the right place for this since it can only convey two states: Success or failure. We really need three states: Success, failure (but don't crash AsyncQueue) and failure (crash AsyncQueue). Given this and the test ability, I moved the code back to EventListener. It's not as clean looking, but it will probably be easier to keep regression free. |
||
const query = listener.query; | ||
let firstListen = false; | ||
|
||
let queryInfo = this.queries.get(query); | ||
if (!queryInfo) { | ||
firstListen = true; | ||
queryInfo = new QueryListenersInfo(); | ||
this.queries.set(query, queryInfo); | ||
} | ||
|
||
if (firstListen) { | ||
try { | ||
queryInfo.viewSnap = await this.syncEngine.listen(query); | ||
} catch (e) { | ||
const msg = `Initialization of query '${query}' failed: ${e}`; | ||
logError(LOG_TAG, msg); | ||
if (e.name === 'IndexedDbTransactionError') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This may not be possible, but it seems like could benefit from handling these errors consistently in our API. For example, we try to make FirestoreClient the place where we centrally handle dispatching onto the AsyncQueue. Should we try to similarly make handling retries (or error handling like this) a part of the SyncEngine? This doesn't seem to work in this case because you're returning a successful promise in case of a known error, but that seems to be catering to the caller's use of this function in a way that makes this more complicated than it needs to be. Maybe the caller should handle this in the block submitted to the async queue? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did end up updating this, but as stated this pushes the error handling to both FirestoreClient and SpecTestRunner. |
||
listener.onError(new FirestoreError(Code.UNAVAILABLE, msg)); | ||
} else { | ||
throw e; | ||
} | ||
return; | ||
} | ||
} | ||
|
||
this.queries.set(query, queryInfo); | ||
queryInfo.listeners.push(listener); | ||
|
||
// Run global snapshot listeners if a consistent snapshot has been emitted. | ||
|
@@ -84,15 +103,6 @@ export class EventManager implements SyncEngineListener { | |
this.raiseSnapshotsInSyncEvent(); | ||
} | ||
} | ||
|
||
if (firstListen) { | ||
return this.syncEngine.listen(query).then(targetId => { | ||
queryInfo!.targetId = targetId; | ||
return targetId; | ||
}); | ||
} else { | ||
return Promise.resolve(queryInfo.targetId); | ||
} | ||
} | ||
|
||
async unlisten(listener: QueryListener): Promise<void> { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -213,9 +213,9 @@ export class SyncEngine implements RemoteSyncer { | |
/** | ||
* Initiates the new listen, resolves promise when listen enqueued to the | ||
* server. All the subsequent view snapshots or errors are sent to the | ||
* subscribed handlers. Returns the targetId of the query. | ||
* subscribed handlers. Returns the initial snapshot. | ||
*/ | ||
async listen(query: Query): Promise<TargetId> { | ||
async listen(query: Query): Promise<ViewSnapshot> { | ||
this.assertSubscribed('listen()'); | ||
|
||
let targetId; | ||
|
@@ -249,8 +249,7 @@ export class SyncEngine implements RemoteSyncer { | |
} | ||
} | ||
|
||
this.syncEngineListener!.onWatchChange([viewSnapshot]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had to remove this indirection. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At first I was concerned about this because it seemed like this would prevent calling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure I fully understand this, but I hope that my previous comment reliefs your concerns. Let me know if it doesn't. |
||
return targetId; | ||
return viewSnapshot; | ||
} | ||
|
||
/** | ||
|
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 and the next release note could probably be combined. Something like:
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 prior change is going out this week. Unfortunately, this Changelog is not all that clean, but this is mostly due to the fact the version numbers are impossible to figure out. Maybe we can discuss this with the team and clean it up once and for all.