Skip to content

Commit e20a185

Browse files
2 parents bae5f1e + 74c57cf commit e20a185

File tree

5 files changed

+99
-125
lines changed

5 files changed

+99
-125
lines changed

packages/firestore/CHANGELOG.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
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
1+
# 0.7.0 (Unreleased)
82
- [fixed] Fixed `get({source: 'cache'})` to be able to return nonexistent
93
documents from cache.
104
- [changed] Prepared the persistence layer to allow shared access from multiple

packages/firestore/src/remote/online_state_tracker.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,7 @@ 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-
// 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;
28+
const MAX_WATCH_STREAM_FAILURES = 2;
3229

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

packages/firestore/src/remote/remote_store.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -243,16 +243,8 @@ export class RemoteStore implements TargetMetadataProvider {
243243
delete this.listenTargets[targetId];
244244
if (this.watchStream.isOpen()) {
245245
this.sendUnwatchRequest(targetId);
246-
}
247-
248-
if (objUtils.isEmpty(this.listenTargets)) {
249-
if (this.watchStream.isOpen()) {
246+
if (objUtils.isEmpty(this.listenTargets)) {
250247
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);
256248
}
257249
}
258250
}
@@ -338,6 +330,7 @@ export class RemoteStore implements TargetMetadataProvider {
338330
// If we still need the watch stream, retry the connection.
339331
if (this.shouldStartWatchStream()) {
340332
this.onlineStateTracker.handleWatchStreamFailure(error);
333+
341334
this.startWatchStream();
342335
} else {
343336
// No need to restart watch stream because there are no active targets.

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

Lines changed: 94 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -23,60 +23,56 @@ import { spec } from './spec_builder';
2323
import { TimerId } from '../../../src/util/async_queue';
2424

2525
describeSpec('Offline:', [], () => {
26-
specTest(
27-
'Empty queries are resolved if client goes offline',
28-
['no-android', 'no-ios'],
29-
() => {
30-
const query = Query.atPath(path('collection'));
31-
return (
32-
spec()
33-
.userListens(query)
34-
.watchStreamCloses(Code.UNAVAILABLE)
35-
.expectEvents(query, {
36-
fromCache: true,
37-
hasPendingWrites: false
38-
})
39-
// no further events
40-
.watchStreamCloses(Code.UNAVAILABLE)
41-
.watchStreamCloses(Code.UNAVAILABLE)
42-
);
43-
}
44-
);
26+
specTest('Empty queries are resolved if client goes offline', [], () => {
27+
const query = Query.atPath(path('collection'));
28+
return (
29+
spec()
30+
.userListens(query)
31+
// second error triggers event
32+
.watchStreamCloses(Code.UNAVAILABLE)
33+
.watchStreamCloses(Code.UNAVAILABLE)
34+
.expectEvents(query, {
35+
fromCache: true,
36+
hasPendingWrites: false
37+
})
38+
// no further events
39+
.watchStreamCloses(Code.UNAVAILABLE)
40+
.watchStreamCloses(Code.UNAVAILABLE)
41+
);
42+
});
4543

46-
specTest(
47-
'A successful message delays offline status',
48-
['no-android', 'no-ios'],
49-
() => {
50-
const query = Query.atPath(path('collection'));
51-
return (
52-
spec()
53-
.userListens(query)
54-
.watchAcks(query)
55-
// first error triggers unknown state
56-
.watchStreamCloses(Code.UNAVAILABLE)
57-
// second error triggers offline state
58-
.watchStreamCloses(Code.UNAVAILABLE)
59-
.expectEvents(query, {
60-
fromCache: true,
61-
hasPendingWrites: false
62-
})
63-
// no further events
64-
.watchStreamCloses(Code.UNAVAILABLE)
65-
.watchStreamCloses(Code.UNAVAILABLE)
66-
);
67-
}
68-
);
44+
specTest('A successful message delays offline status', [], () => {
45+
const query = Query.atPath(path('collection'));
46+
return (
47+
spec()
48+
.userListens(query)
49+
.watchAcks(query)
50+
// first error triggers unknown state
51+
.watchStreamCloses(Code.UNAVAILABLE)
52+
// getting two more errors triggers offline state
53+
.watchStreamCloses(Code.UNAVAILABLE)
54+
.watchStreamCloses(Code.UNAVAILABLE)
55+
.expectEvents(query, {
56+
fromCache: true,
57+
hasPendingWrites: false
58+
})
59+
// no further events
60+
.watchStreamCloses(Code.UNAVAILABLE)
61+
.watchStreamCloses(Code.UNAVAILABLE)
62+
);
63+
});
6964

7065
specTest(
7166
'Removing all listeners delays "Offline" status on next listen',
72-
['eager-gc', 'no-android', 'no-ios'],
67+
['eager-gc'],
7368
'Marked as no-lru because when a listen is re-added, it gets a new target id rather than reusing one',
7469
() => {
7570
const query = Query.atPath(path('collection'));
7671
return (
7772
spec()
7873
.userListens(query)
79-
// error triggers offline state
74+
// getting two errors triggers offline state
75+
.watchStreamCloses(Code.UNAVAILABLE)
8076
.watchStreamCloses(Code.UNAVAILABLE)
8177
.expectEvents(query, {
8278
fromCache: true,
@@ -87,10 +83,11 @@ describeSpec('Offline:', [], () => {
8783
// If the next (already scheduled) connection attempt fails, we'll move
8884
// to unknown since there are no listeners, and stop trying to connect.
8985
.watchStreamCloses(Code.UNAVAILABLE)
90-
// Suppose sometime later we listen again, it should take one failure
86+
// Suppose sometime later we listen again, it should take two failures
9187
// before we get cached data.
9288
.userListens(query)
9389
.watchStreamCloses(Code.UNAVAILABLE)
90+
.watchStreamCloses(Code.UNAVAILABLE)
9491
.expectEvents(query, {
9592
fromCache: true,
9693
hasPendingWrites: false
@@ -99,62 +96,56 @@ describeSpec('Offline:', [], () => {
9996
}
10097
);
10198

102-
specTest(
103-
'Queries revert to fromCache=true when offline.',
104-
['no-android', 'no-ios'],
105-
() => {
106-
const query = Query.atPath(path('collection'));
107-
const docA = doc('collection/a', 1000, { key: 'a' });
108-
return (
109-
spec()
110-
.userListens(query)
111-
.watchAcksFull(query, 1000, docA)
112-
.expectEvents(query, { added: [docA] })
113-
// first error triggers unknown state
114-
.watchStreamCloses(Code.UNAVAILABLE)
115-
.restoreListen(query, 'resume-token-1000')
116-
// second error triggers offline state and fromCache: true
117-
.watchStreamCloses(Code.UNAVAILABLE)
118-
.expectEvents(query, { fromCache: true })
119-
// Going online and getting a CURRENT message triggers fromCache: false
120-
.watchAcksFull(query, 1000)
121-
.expectEvents(query, { fromCache: false })
122-
);
123-
}
124-
);
99+
specTest('Queries revert to fromCache=true when offline.', [], () => {
100+
const query = Query.atPath(path('collection'));
101+
const docA = doc('collection/a', 1000, { key: 'a' });
102+
return (
103+
spec()
104+
.userListens(query)
105+
.watchAcksFull(query, 1000, docA)
106+
.expectEvents(query, { added: [docA] })
107+
// first error triggers unknown state
108+
.watchStreamCloses(Code.UNAVAILABLE)
109+
.restoreListen(query, 'resume-token-1000')
110+
// getting two more errors triggers offline state and fromCache: true
111+
.watchStreamCloses(Code.UNAVAILABLE)
112+
.watchStreamCloses(Code.UNAVAILABLE)
113+
.expectEvents(query, { fromCache: true })
114+
// Going online and getting a CURRENT message triggers fromCache: false
115+
.watchAcksFull(query, 1000)
116+
.expectEvents(query, { fromCache: false })
117+
);
118+
});
125119

126-
specTest(
127-
'Queries with limbo documents handle going offline.',
128-
['no-android', 'no-ios'],
129-
() => {
130-
const query = Query.atPath(path('collection'));
131-
const docA = doc('collection/a', 1000, { key: 'a' });
132-
const limboQuery = Query.atPath(docA.key.path);
133-
return (
134-
spec()
135-
.userListens(query)
136-
.watchAcksFull(query, 1000, docA)
137-
.expectEvents(query, { added: [docA] })
138-
.watchResets(query)
139-
// No more documents
140-
.watchCurrents(query, 'resume-token-1001')
141-
.watchSnapshots(1001)
142-
// docA will now be in limbo (triggering fromCache=true)
143-
.expectLimboDocs(docA.key)
144-
.expectEvents(query, { fromCache: true })
145-
// first error triggers unknown state
146-
.watchStreamCloses(Code.UNAVAILABLE)
147-
.restoreListen(query, 'resume-token-1001')
148-
// second error triggers offline state.
149-
.watchStreamCloses(Code.UNAVAILABLE)
150-
.watchAcksFull(query, 1001)
151-
.watchAcksFull(limboQuery, 1001)
152-
// Limbo document is resolved. No longer from cache.
153-
.expectEvents(query, { removed: [docA], fromCache: false })
154-
.expectLimboDocs()
155-
);
156-
}
157-
);
120+
specTest('Queries with limbo documents handle going offline.', [], () => {
121+
const query = Query.atPath(path('collection'));
122+
const docA = doc('collection/a', 1000, { key: 'a' });
123+
const limboQuery = Query.atPath(docA.key.path);
124+
return (
125+
spec()
126+
.userListens(query)
127+
.watchAcksFull(query, 1000, docA)
128+
.expectEvents(query, { added: [docA] })
129+
.watchResets(query)
130+
// No more documents
131+
.watchCurrents(query, 'resume-token-1001')
132+
.watchSnapshots(1001)
133+
// docA will now be in limbo (triggering fromCache=true)
134+
.expectLimboDocs(docA.key)
135+
.expectEvents(query, { fromCache: true })
136+
// first error triggers unknown state
137+
.watchStreamCloses(Code.UNAVAILABLE)
138+
.restoreListen(query, 'resume-token-1001')
139+
// getting two more errors triggers offline state.
140+
.watchStreamCloses(Code.UNAVAILABLE)
141+
.watchStreamCloses(Code.UNAVAILABLE)
142+
.watchAcksFull(query, 1001)
143+
.watchAcksFull(limboQuery, 1001)
144+
// Limbo document is resolved. No longer from cache.
145+
.expectEvents(query, { removed: [docA], fromCache: false })
146+
.expectLimboDocs()
147+
);
148+
});
158149

159150
specTest('OnlineState timeout triggers offline behavior', [], () => {
160151
const query = Query.atPath(path('collection'));
@@ -193,16 +184,17 @@ describeSpec('Offline:', [], () => {
193184
specTest(
194185
'New queries return immediately with fromCache=true when offline due to ' +
195186
'stream failures.',
196-
['no-android', 'no-ios'],
187+
[],
197188
() => {
198189
const query1 = Query.atPath(path('collection'));
199190
const query2 = Query.atPath(path('collection2'));
200191
return (
201192
spec()
202193
.userListens(query1)
203-
// After failure, we mark the client offline and trigger an empty
194+
// 2 Failures should mark the client offline and trigger an empty
204195
// fromCache event.
205196
.watchStreamCloses(Code.UNAVAILABLE)
197+
.watchStreamCloses(Code.UNAVAILABLE)
206198
.expectEvents(query1, { fromCache: true })
207199

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

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ describeSpec('Remote store:', [], () => {
6767
.expectEvents(query, { added: [doc4] }); // This should work now.
6868
});
6969

70-
specTest('Cleans up watch state correctly', ['no-android', 'no-ios'], () => {
70+
specTest('Cleans up watch state correctly', [], () => {
7171
const query = Query.atPath(path('collection'));
7272
const doc1 = doc('collection/a', 1000, { key: 'a' });
7373
return (
@@ -77,7 +77,6 @@ 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 })
8180
// This should work now.
8281
.watchAcksFull(query, 1001, doc1)
8382
.expectEvents(query, { added: [doc1] })
@@ -98,7 +97,6 @@ describeSpec('Remote store:', [], () => {
9897
// close the stream (this should trigger retry with backoff; but don't
9998
// run it in an attempt to reproduce b/74749605).
10099
.watchStreamCloses(Code.UNAVAILABLE, { runBackoffTimer: false })
101-
.expectEvents(query, { fromCache: true })
102100

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

0 commit comments

Comments
 (0)