Skip to content

Commit 0e014e4

Browse files
committed
store: Retry polling event queue on error
The new TODO comments are all things that we don't do today in zulip-mobile, and haven't been aware of causing problems for people. So there's no rush to do them here. Fixes: #184
1 parent 62d2913 commit 0e014e4

File tree

2 files changed

+65
-3
lines changed

2 files changed

+65
-3
lines changed

lib/model/store.dart

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import '../api/model/initial_snapshot.dart';
1414
import '../api/model/model.dart';
1515
import '../api/route/events.dart';
1616
import '../api/route/messages.dart';
17+
import '../api/backoff.dart';
1718
import '../log.dart';
1819
import '../notifications.dart';
1920
import 'autocomplete.dart';
@@ -573,6 +574,8 @@ class UpdateMachine {
573574
}
574575

575576
void poll() async {
577+
final backoffMachine = BackoffMachine();
578+
576579
while (true) {
577580
if (_debugLoopSignal != null) {
578581
await _debugLoopSignal!.future;
@@ -595,10 +598,22 @@ class UpdateMachine {
595598
debugLog('… Event queue replaced.');
596599
return;
597600

601+
case Server5xxException() || NetworkException():
602+
assert(debugLog('Transient error polling event queue for $store: $e\n'
603+
'Backing off, then will retry…'));
604+
// TODO tell user if transient polling errors persist
605+
// TODO reset to short backoff eventually
606+
await backoffMachine.wait();
607+
assert(debugLog('… Backoff wait complete, retrying poll.'));
608+
continue;
609+
598610
default:
599-
assert(debugLog('Error polling event queue for $store: $e'));
600-
// TODO(#184) handle errors on get-events; retry with backoff
601-
rethrow;
611+
assert(debugLog('Error polling event queue for $store: $e\n'
612+
'Backing off and retrying even though may be hopeless…'));
613+
// TODO tell user on non-transient error in polling
614+
await backoffMachine.wait();
615+
assert(debugLog('… Backoff wait complete, retrying poll.'));
616+
continue;
602617
}
603618
}
604619

test/model/store_test.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,53 @@ void main() {
249249
await Future.delayed(Duration.zero);
250250
check(store.userSettings!.twentyFourHourTime).isTrue();
251251
}));
252+
253+
void checkRetry(void Function() prepareError) {
254+
awaitFakeAsync((async) async {
255+
await prepareStore(lastEventId: 1);
256+
updateMachine.debugPauseLoop();
257+
updateMachine.poll();
258+
check(async.pendingTimers).length.equals(0);
259+
260+
// Make the request, inducing an error in it.
261+
prepareError();
262+
updateMachine.debugAdvanceLoop();
263+
async.flushMicrotasks();
264+
checkLastRequest(lastEventId: 1);
265+
266+
// Polling doesn't resume immediately; there's a timer.
267+
check(async.pendingTimers).length.equals(1);
268+
updateMachine.debugAdvanceLoop();
269+
async.flushMicrotasks();
270+
check(connection.lastRequest).isNull();
271+
check(async.pendingTimers).length.equals(1);
272+
273+
// Polling continues after a timer.
274+
connection.prepare(json: GetEventsResult(events: [
275+
HeartbeatEvent(id: 2),
276+
], queueId: null).toJson());
277+
async.flushTimers();
278+
checkLastRequest(lastEventId: 1);
279+
check(updateMachine.lastEventId).equals(2);
280+
});
281+
}
282+
283+
test('retries on Server5xxException', () {
284+
checkRetry(() => connection.prepare(httpStatus: 500, body: 'splat'));
285+
});
286+
287+
test('retries on NetworkException', () {
288+
checkRetry(() => connection.prepare(exception: Exception("failed")));
289+
});
290+
291+
test('retries on ZulipApiException', () {
292+
checkRetry(() => connection.prepare(httpStatus: 400, json: {
293+
'result': 'error', 'code': 'BAD_REQUEST', 'msg': 'Bad request'}));
294+
});
295+
296+
test('retries on MalformedServerResponseException', () {
297+
checkRetry(() => connection.prepare(httpStatus: 200, body: 'nonsense'));
298+
});
252299
});
253300

254301
group('UpdateMachine.registerNotificationToken', () {

0 commit comments

Comments
 (0)