Skip to content

Commit d90beab

Browse files
authored
Fix issue that could cause offline get()s to wait up to 10 seconds. (#592)
OnlineStateTracker was reverting to OnlineState Unknown on every stream attempt rather than remaining Offline once the offline heuristic had been met (i.e. 2 stream failures or 10 seconds). This means that get() requests made while offline could be delayed up to 10 seconds each time (or until the next backoff attempt failed).
1 parent bb93e47 commit d90beab

File tree

3 files changed

+63
-5
lines changed

3 files changed

+63
-5
lines changed

packages/firestore/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
# Unreleased
2+
- [fixed] Fixed a regression in the Firebase JS release 4.11.0 that could
3+
cause get() requests made while offline to be delayed by up to 10
4+
seconds (rather than returning from cache immediately).
5+
6+
# 0.3.6
27
- [fixed] Fixed a regression in the Firebase JS release 4.11.0 that could
38
cause a crash if a user signs out while the client is offline, resulting in
49
an error of "Attempted to schedule multiple operations with timer id

packages/firestore/src/remote/online_state_tracker.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,20 @@ export class OnlineStateTracker {
7474
) {}
7575

7676
/**
77-
* Called by RemoteStore when a watch stream is started.
77+
* Called by RemoteStore when a watch stream is started (including on each
78+
* backoff attempt).
7879
*
79-
* It sets the OnlineState to Unknown and starts the onlineStateTimer
80-
* if necessary.
80+
* If this is the first attempt, it sets the OnlineState to Unknown and starts
81+
* the onlineStateTimer.
8182
*/
8283
handleWatchStreamStart(): void {
83-
this.setAndBroadcast(OnlineState.Unknown);
84+
if (this.watchStreamFailures === 0) {
85+
this.setAndBroadcast(OnlineState.Unknown);
8486

85-
if (this.onlineStateTimer === null) {
87+
assert(
88+
this.onlineStateTimer === null,
89+
`onlineStateTimer shouldn't be started yet`
90+
);
8691
this.onlineStateTimer = this.asyncQueue.enqueueAfterDelay(
8792
TimerId.OnlineStateTimeout,
8893
ONLINE_STATE_TIMEOUT_MS,
@@ -119,6 +124,11 @@ export class OnlineStateTracker {
119124
handleWatchStreamFailure(): void {
120125
if (this.state === OnlineState.Online) {
121126
this.setAndBroadcast(OnlineState.Unknown);
127+
128+
// To get to OnlineState.Online, set() must have been called which would
129+
// have reset our heuristics.
130+
assert(this.watchStreamFailures === 0, 'watchStreamFailures must be 0');
131+
assert(this.onlineStateTimer === null, 'onlineStateTimer must be null');
122132
} else {
123133
this.watchStreamFailures++;
124134
if (this.watchStreamFailures >= MAX_WATCH_STREAM_FAILURES) {

packages/firestore/test/unit/specs/offline_spec.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,4 +181,47 @@ describeSpec('Offline:', [], () => {
181181
})
182182
);
183183
});
184+
185+
specTest(
186+
'New queries return immediately with fromCache=true when offline due to ' +
187+
'stream failures.',
188+
[],
189+
() => {
190+
const query1 = Query.atPath(path('collection'));
191+
const query2 = Query.atPath(path('collection2'));
192+
return (
193+
spec()
194+
.userListens(query1)
195+
// 2 Failures should mark the client offline and trigger an empty
196+
// fromCache event.
197+
.watchStreamCloses(Code.UNAVAILABLE)
198+
.watchStreamCloses(Code.UNAVAILABLE)
199+
.expectEvents(query1, { fromCache: true })
200+
201+
// A new query should immediately return from cache.
202+
.userListens(query2)
203+
.expectEvents(query2, { fromCache: true })
204+
);
205+
}
206+
);
207+
208+
specTest(
209+
'New queries return immediately with fromCache=true when offline due to ' +
210+
'OnlineState timeout.',
211+
[],
212+
() => {
213+
const query1 = Query.atPath(path('collection'));
214+
const query2 = Query.atPath(path('collection2'));
215+
return (
216+
spec()
217+
.userListens(query1)
218+
.runTimer(TimerId.OnlineStateTimeout)
219+
.expectEvents(query1, { fromCache: true })
220+
221+
// A new query should immediately return from cache.
222+
.userListens(query2)
223+
.expectEvents(query2, { fromCache: true })
224+
);
225+
}
226+
);
184227
});

0 commit comments

Comments
 (0)