Skip to content

Commit c7e1ec9

Browse files
author
Michael Lehenbauer
committed
Offline get() improvements.
1. Changed MAX_WATCH_STREAM_FAILURES to 1 per conversation with @wilhuff and @jdimond. 2. Fixed a "race" where when a get() triggered a watch stream error, we'd restart the stream, then get() would remove its listener, and so we had no listener left once the watch stream was "opened". Without a listen to send we'd be stuck in Offline state (even though we should be Unknown whenever we have no listens to send), resulting in the next get() returning data from cache without even attempting to reach the backend.
1 parent 95f3d18 commit c7e1ec9

File tree

5 files changed

+28
-18
lines changed

5 files changed

+28
-18
lines changed

packages/firestore/CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
# 0.7.0 (Unreleased)
1+
# 0.7.1 (Unreleased)
2+
- [fixed] Fixed an issue where the first `get()` call made after being offline
3+
could incorrectly return cached data without attempting to reach the backend.
4+
- [changed] Changed `get()` to only make 1 attempt to reach the backend before
5+
returning cached data, potentially reducing delays while offline.
6+
7+
# 0.7.0
28
- [fixed] Fixed `get({source: 'cache'})` to be able to return nonexistent
39
documents from cache.
410
- [changed] Prepared the persistence layer to allow shared access from multiple

packages/firestore/src/remote/online_state_tracker.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ const LOG_TAG = 'OnlineStateTracker';
2525

2626
// To deal with transient failures, we allow multiple stream attempts before
2727
// giving up and transitioning from OnlineState.Unknown to Offline.
28-
const MAX_WATCH_STREAM_FAILURES = 2;
28+
// TODO(mikelehen): This used to be set to 2 as a mitigation for b/66228394.
29+
// @jdimond thinks that bug is sufficiently fixed so that we can set this back
30+
// to 1. If that works okay, we could potentially remove this logic entirely.
31+
const MAX_WATCH_STREAM_FAILURES = 1;
2932

3033
// To deal with stream attempts that don't succeed or fail in a timely manner,
3134
// we have a timeout for OnlineState to reach Online or Offline.

packages/firestore/src/remote/remote_store.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,16 @@ export class RemoteStore implements TargetMetadataProvider {
243243
delete this.listenTargets[targetId];
244244
if (this.watchStream.isOpen()) {
245245
this.sendUnwatchRequest(targetId);
246-
if (objUtils.isEmpty(this.listenTargets)) {
246+
}
247+
248+
if (objUtils.isEmpty(this.listenTargets)) {
249+
if (this.watchStream.isOpen()) {
247250
this.watchStream.markIdle();
251+
} else {
252+
// Revert to OnlineState.Unknown if the watch stream is not open and we
253+
// have no listeners, since without any listens to send we cannot
254+
// confirm if the stream is healthy and upgrade to OnlineState.Online.
255+
this.onlineStateTracker.set(OnlineState.Unknown);
248256
}
249257
}
250258
}
@@ -330,7 +338,6 @@ export class RemoteStore implements TargetMetadataProvider {
330338
// If we still need the watch stream, retry the connection.
331339
if (this.shouldStartWatchStream()) {
332340
this.onlineStateTracker.handleWatchStreamFailure(error);
333-
334341
this.startWatchStream();
335342
} else {
336343
// No need to restart watch stream because there are no active targets.

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

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ describeSpec('Offline:', [], () => {
2828
return (
2929
spec()
3030
.userListens(query)
31-
// second error triggers event
32-
.watchStreamCloses(Code.UNAVAILABLE)
3331
.watchStreamCloses(Code.UNAVAILABLE)
3432
.expectEvents(query, {
3533
fromCache: true,
@@ -49,8 +47,7 @@ describeSpec('Offline:', [], () => {
4947
.watchAcks(query)
5048
// first error triggers unknown state
5149
.watchStreamCloses(Code.UNAVAILABLE)
52-
// getting two more errors triggers offline state
53-
.watchStreamCloses(Code.UNAVAILABLE)
50+
// second error triggers offline state
5451
.watchStreamCloses(Code.UNAVAILABLE)
5552
.expectEvents(query, {
5653
fromCache: true,
@@ -71,8 +68,7 @@ describeSpec('Offline:', [], () => {
7168
return (
7269
spec()
7370
.userListens(query)
74-
// getting two errors triggers offline state
75-
.watchStreamCloses(Code.UNAVAILABLE)
71+
// error triggers offline state
7672
.watchStreamCloses(Code.UNAVAILABLE)
7773
.expectEvents(query, {
7874
fromCache: true,
@@ -83,11 +79,10 @@ describeSpec('Offline:', [], () => {
8379
// If the next (already scheduled) connection attempt fails, we'll move
8480
// to unknown since there are no listeners, and stop trying to connect.
8581
.watchStreamCloses(Code.UNAVAILABLE)
86-
// Suppose sometime later we listen again, it should take two failures
82+
// Suppose sometime later we listen again, it should take one failure
8783
// before we get cached data.
8884
.userListens(query)
8985
.watchStreamCloses(Code.UNAVAILABLE)
90-
.watchStreamCloses(Code.UNAVAILABLE)
9186
.expectEvents(query, {
9287
fromCache: true,
9388
hasPendingWrites: false
@@ -107,8 +102,7 @@ describeSpec('Offline:', [], () => {
107102
// first error triggers unknown state
108103
.watchStreamCloses(Code.UNAVAILABLE)
109104
.restoreListen(query, 'resume-token-1000')
110-
// getting two more errors triggers offline state and fromCache: true
111-
.watchStreamCloses(Code.UNAVAILABLE)
105+
// second error triggers offline state and fromCache: true
112106
.watchStreamCloses(Code.UNAVAILABLE)
113107
.expectEvents(query, { fromCache: true })
114108
// Going online and getting a CURRENT message triggers fromCache: false
@@ -136,8 +130,7 @@ describeSpec('Offline:', [], () => {
136130
// first error triggers unknown state
137131
.watchStreamCloses(Code.UNAVAILABLE)
138132
.restoreListen(query, 'resume-token-1001')
139-
// getting two more errors triggers offline state.
140-
.watchStreamCloses(Code.UNAVAILABLE)
133+
// second error triggers offline state.
141134
.watchStreamCloses(Code.UNAVAILABLE)
142135
.watchAcksFull(query, 1001)
143136
.watchAcksFull(limboQuery, 1001)
@@ -191,10 +184,9 @@ describeSpec('Offline:', [], () => {
191184
return (
192185
spec()
193186
.userListens(query1)
194-
// 2 Failures should mark the client offline and trigger an empty
187+
// After failure, we mark the client offline and trigger an empty
195188
// fromCache event.
196189
.watchStreamCloses(Code.UNAVAILABLE)
197-
.watchStreamCloses(Code.UNAVAILABLE)
198190
.expectEvents(query1, { fromCache: true })
199191

200192
// A new query should immediately return from cache.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ describeSpec('Remote store:', [], () => {
7777
// Close before we get an ack, this should reset our pending
7878
// target counts.
7979
.watchStreamCloses(Code.UNAVAILABLE)
80+
.expectEvents(query, { fromCache: true })
8081
// This should work now.
8182
.watchAcksFull(query, 1001, doc1)
8283
.expectEvents(query, { added: [doc1] })
@@ -97,6 +98,7 @@ describeSpec('Remote store:', [], () => {
9798
// close the stream (this should trigger retry with backoff; but don't
9899
// run it in an attempt to reproduce b/74749605).
99100
.watchStreamCloses(Code.UNAVAILABLE, { runBackoffTimer: false })
101+
.expectEvents(query, { fromCache: true })
100102

101103
// Because we didn't let the backoff timer run and restart the watch
102104
// stream, there will be no active targets.

0 commit comments

Comments
 (0)