Skip to content

Commit 548f391

Browse files
committed
store: Reset backoff duration on polling success.
Fixes: zulip#554 Signed-off-by: Zixuan James Li <[email protected]>
1 parent 108c551 commit 548f391

File tree

2 files changed

+66
-3
lines changed

2 files changed

+66
-3
lines changed

lib/model/store.dart

+4-3
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,7 @@ class UpdateMachine {
750750
}
751751

752752
void poll() async {
753-
final backoffMachine = BackoffMachine();
753+
BackoffMachine? backoffMachine;
754754

755755
while (true) {
756756
if (_debugLoopSignal != null) {
@@ -779,20 +779,21 @@ class UpdateMachine {
779779
'Backing off, then will retry…'));
780780
// TODO tell user if transient polling errors persist
781781
// TODO reset to short backoff eventually
782-
await backoffMachine.wait();
782+
await (backoffMachine ??= BackoffMachine()).wait();
783783
assert(debugLog('… Backoff wait complete, retrying poll.'));
784784
continue;
785785

786786
default:
787787
assert(debugLog('Error polling event queue for $store: $e\n'
788788
'Backing off and retrying even though may be hopeless…'));
789789
// TODO tell user on non-transient error in polling
790-
await backoffMachine.wait();
790+
await (backoffMachine ??= BackoffMachine()).wait();
791791
assert(debugLog('… Backoff wait complete, retrying poll.'));
792792
continue;
793793
}
794794
}
795795

796+
backoffMachine = null;
796797
final events = result.events;
797798
for (final event in events) {
798799
await store.handleEvent(event);

test/model/store_test.dart

+62
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:async';
22

33
import 'package:checks/checks.dart';
4+
import 'package:fake_async/fake_async.dart';
45
import 'package:flutter/foundation.dart';
56
import 'package:http/http.dart' as http;
67
import 'package:test/scaffolding.dart';
@@ -11,6 +12,7 @@ import 'package:zulip/api/route/messages.dart';
1112
import 'package:zulip/model/store.dart';
1213
import 'package:zulip/notifications/receive.dart';
1314

15+
import '../api/backoff_test.dart';
1416
import '../api/fake_api.dart';
1517
import '../api/model/model_checks.dart';
1618
import '../example_data.dart' as eg;
@@ -425,6 +427,66 @@ void main() {
425427
check(store.userSettings!.twentyFourHourTime).isTrue();
426428
}));
427429

430+
test('reset BackoffMachine after a successful poll', () {
431+
Duration measureWait(FakeAsync async) {
432+
final duration = async.pendingTimers.first.duration;
433+
async.flushTimers();
434+
return duration;
435+
}
436+
437+
void prepareError() {
438+
connection.prepare(httpStatus: 500, body: 'splat');
439+
}
440+
void prepareSuccess() {
441+
connection.prepare(json: GetEventsResult(events: [
442+
HeartbeatEvent(id: 2),
443+
], queueId: null).toJson());
444+
}
445+
446+
verifyRandomizedDurations(
447+
expectedMaxDurationsInMs: [100, 200, 400, 800, 1600, 100, 200, 400, 800, 1600],
448+
computeActualDurations: (expectedMaxDurations) => awaitFakeAsync((async) async {
449+
await prepareStore();
450+
updateMachine.debugPauseLoop();
451+
updateMachine.poll();
452+
final results = <Duration>[];
453+
int lastEventId = 1;
454+
for (int i = 0; i < expectedMaxDurations.length; i++) {
455+
check(async.pendingTimers).isEmpty();
456+
if (i > 0 && expectedMaxDurations[i] < expectedMaxDurations[i - 1]) {
457+
// The backoff duration should get a reset when the poll was
458+
// successful.
459+
prepareSuccess();
460+
updateMachine.debugAdvanceLoop();
461+
async.flushTimers();
462+
463+
checkLastRequest(lastEventId: lastEventId);
464+
// Verify that the poll completes successfully.
465+
lastEventId++;
466+
check(updateMachine.lastEventId).equals(lastEventId);
467+
}
468+
469+
// Make the request, inducing an error in it.
470+
prepareError();
471+
updateMachine.debugAdvanceLoop();
472+
473+
// Wait for the request to finish.
474+
async.elapse(Duration.zero);
475+
checkLastRequest(lastEventId: lastEventId);
476+
check(updateMachine.lastEventId).equals(lastEventId);
477+
478+
// The backoff timer should be active now.
479+
check(async.pendingTimers).single;
480+
final duration = measureWait(async);
481+
results.add(duration);
482+
check(async.pendingTimers).isEmpty();
483+
check(connection.lastRequest).isNull();
484+
}
485+
486+
return results;
487+
}));
488+
});
489+
428490
void checkRetry(void Function() prepareError) {
429491
awaitFakeAsync((async) async {
430492
await prepareStore(lastEventId: 1);

0 commit comments

Comments
 (0)