-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathRefreshTimer.ts
85 lines (71 loc) · 3.19 KB
/
RefreshTimer.ts
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
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
/**
* The backoff time is between the minimum and maximum backoff time, based on the number of attempts.
* An exponential backoff strategy is used, with a jitter factor to prevent clients from retrying at the same time.
*
* The backoff time is calculated as follows:
* - `basic backoff time` = `MinimumBackoffInMs` * 2 ^ `attempts`, and it is no larger than the `MaximumBackoffInMs`.
* - based on jitter ratio, the jittered time is between [-1, 1) * `JitterRatio` * basic backoff time.
* - the final backoff time is the basic backoff time plus the jittered time.
*
* Note: the backoff time usually is no larger than the refresh interval, which is specified by the user.
* - If the interval is less than the minimum backoff, the interval is used.
* - If the interval is between the minimum and maximum backoff, the interval is used as the maximum backoff.
* - Because of the jitter, the maximum backoff time is actually `MaximumBackoffInMs` * (1 + `JitterRatio`).
*/
const MIN_BACKOFF_IN_MS = 30 * 1000; // 30s
const MAX_BACKOFF_IN_MS = 10 * 60 * 1000; // 10min
const MAX_SAFE_EXPONENTIAL = 30; // Used to avoid overflow. bitwise operations in JavaScript are limited to 32 bits. It overflows at 2^31 - 1.
const JITTER_RATIO = 0.25;
export class RefreshTimer {
#minBackoff: number = MIN_BACKOFF_IN_MS;
#maxBackoff: number = MAX_BACKOFF_IN_MS;
#failedAttempts: number = 0;
#backoffEnd: number; // Timestamp
#interval: number;
constructor(
interval: number
) {
if (interval <= 0) {
throw new Error(`Refresh interval must be greater than 0. Given: ${this.#interval}`);
}
this.#interval = interval;
this.#backoffEnd = Date.now() + this.#interval;
}
canRefresh(): boolean {
return Date.now() >= this.#backoffEnd;
}
backoff(): void {
this.#failedAttempts += 1;
this.#backoffEnd = Date.now() + this.#calculateBackoffTime();
}
reset(): void {
this.#failedAttempts = 0;
this.#backoffEnd = Date.now() + this.#interval;
}
#calculateBackoffTime(): number {
let minBackoffMs: number;
let maxBackoffMs: number;
if (this.#interval <= this.#minBackoff) {
return this.#interval;
}
// _minBackoff <= _interval
if (this.#interval <= this.#maxBackoff) {
minBackoffMs = this.#minBackoff;
maxBackoffMs = this.#interval;
} else {
minBackoffMs = this.#minBackoff;
maxBackoffMs = this.#maxBackoff;
}
// exponential: minBackoffMs * 2^(failedAttempts-1)
const exponential = Math.min(this.#failedAttempts - 1, MAX_SAFE_EXPONENTIAL);
let calculatedBackoffMs = minBackoffMs * (1 << exponential);
if (calculatedBackoffMs > maxBackoffMs) {
calculatedBackoffMs = maxBackoffMs;
}
// jitter: random value between [-1, 1) * jitterRatio * calculatedBackoffMs
const jitter = JITTER_RATIO * (Math.random() * 2 - 1);
return calculatedBackoffMs * (1 + jitter);
}
}