-
Notifications
You must be signed in to change notification settings - Fork 66
Expand file tree
/
Copy pathbackoff.dart
More file actions
96 lines (81 loc) · 3.04 KB
/
backoff.dart
File metadata and controls
96 lines (81 loc) · 3.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import 'dart:math' as math;
import 'package:meta/meta.dart';
@internal
class ExponentialBackoffSetting {
const ExponentialBackoffSetting({
this.initialDelayMs,
this.backoffFactor,
this.maxDelayMs,
this.jitterFactor,
});
final int? initialDelayMs;
final double? backoffFactor;
final int? maxDelayMs;
final double? jitterFactor;
}
/// A helper for running delayed tasks following an exponential backoff curve
/// between attempts.
///
/// Each delay is made up of a "base" delay which follows the exponential
/// backoff curve, and a "jitter" (+/- 50% by default) that is calculated and
/// added to the base delay. This prevents clients from accidentally
/// synchronizing their delays causing spikes of load to the backend.
///
@internal
class ExponentialBackoff {
ExponentialBackoff({
ExponentialBackoffSetting options = const ExponentialBackoffSetting(),
}) : initialDelayMs = options.initialDelayMs ?? defaultBackOffInitialDelayMs,
backoffFactor = options.backoffFactor ?? defaultBackOffFactor,
maxDelayMs = options.maxDelayMs ?? defaultBackOffMaxDelayMs,
jitterFactor = options.jitterFactor ?? defaultJitterFactor;
static const defaultBackOffInitialDelayMs = 1000;
static const defaultBackOffFactor = 1.5;
static const defaultBackOffMaxDelayMs = 60 * 1000;
static const defaultJitterFactor = 1.0;
static const maxRetryAttempts = 10;
final int initialDelayMs;
final double backoffFactor;
final int maxDelayMs;
final double jitterFactor;
int _retryCount = 0;
int _currentBaseMs = 0;
bool _awaitingBackoffCompletion = false;
/// Returns a future that resolves after currentDelayMs, and increases the
/// delay for any subsequent attempts.
///
/// @return A [Future] that resolves when the current delay elapsed.
Future<void> backoffAndWait() async {
if (_awaitingBackoffCompletion) {
throw Exception('A backoff operation is already in progress.');
}
if (_retryCount > maxRetryAttempts) {
throw Exception('Exceeded maximum number of retries allowed.');
}
final delayWithJitterMs = _currentBaseMs + _jitterDelayMs();
_currentBaseMs = (_currentBaseMs * backoffFactor).toInt();
_currentBaseMs = _currentBaseMs.clamp(initialDelayMs, maxDelayMs);
_retryCount += 1;
_awaitingBackoffCompletion = true;
await Future<void>.delayed(Duration(milliseconds: delayWithJitterMs));
_awaitingBackoffCompletion = false;
}
/// Resets the backoff delay and retry count.
///
/// The very next [backoffAndWait] will have no delay. If it is called again
/// (i.e. due to an error), [initialDelayMs] (plus jitter) will be used, and
/// subsequent ones will increase according to the [backoffFactor].
void reset() {
_retryCount = 0;
_currentBaseMs = 0;
}
/// Resets the backoff delay to the maximum delay (e.g. for use after a
/// RESOURCE_EXHAUSTED error).
void resetToMax() {
_currentBaseMs = maxDelayMs;
}
int _jitterDelayMs() {
return ((math.Random().nextDouble() - 0.5) * jitterFactor * _currentBaseMs)
.toInt();
}
}