forked from shaka-project/shaka-player
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbackoff.js
187 lines (157 loc) · 5.28 KB
/
backoff.js
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.net.Backoff');
goog.require('goog.asserts');
goog.require('shaka.util.Error');
goog.require('shaka.util.Timer');
/**
* Backoff represents delay and backoff state. This is used by NetworkingEngine
* for individual requests and by StreamingEngine to retry streaming failures.
*
* @final
*/
shaka.net.Backoff = class {
/**
* @param {shaka.extern.RetryParameters} parameters
* @param {boolean=} autoReset If true, start at a "first retry" state and
* and auto-reset that state when we reach maxAttempts.
* Default set to false.
*/
constructor(parameters, autoReset = false) {
// Set defaults as we unpack these, so that individual app-level requests in
// NetworkingEngine can be missing parameters.
const defaults = shaka.net.Backoff.defaultRetryParameters();
/**
* @const
* @private {number}
*/
this.maxAttempts_ = (parameters.maxAttempts == null) ?
defaults.maxAttempts : parameters.maxAttempts;
goog.asserts.assert(this.maxAttempts_ >= 1, 'maxAttempts should be >= 1');
/**
* @const
* @private {number}
*/
this.baseDelay_ = (parameters.baseDelay == null) ?
defaults.baseDelay : parameters.baseDelay;
goog.asserts.assert(this.baseDelay_ >= 0, 'baseDelay should be >= 0');
/**
* @const
* @private {number}
*/
this.fuzzFactor_ = (parameters.fuzzFactor == null) ?
defaults.fuzzFactor : parameters.fuzzFactor;
goog.asserts.assert(this.fuzzFactor_ >= 0, 'fuzzFactor should be >= 0');
/**
* @const
* @private {number}
*/
this.backoffFactor_ = (parameters.backoffFactor == null) ?
defaults.backoffFactor : parameters.backoffFactor;
goog.asserts.assert(
this.backoffFactor_ >= 0, 'backoffFactor should be >= 0');
/** @private {number} */
this.numAttempts_ = 0;
/** @private {number} */
this.nextUnfuzzedDelay_ = this.baseDelay_;
/** @private {boolean} */
this.autoReset_ = autoReset;
if (this.autoReset_) {
// There is no delay before the first attempt. In StreamingEngine (the
// intended user of auto-reset mode), the first attempt was implied, so we
// reset numAttempts to 1. Therefore maxAttempts (which includes the
// first attempt) must be at least 2 for us to see a delay.
goog.asserts.assert(this.maxAttempts_ >= 2,
'maxAttempts must be >= 2 for autoReset == true');
this.numAttempts_ = 1;
}
}
/**
* @return {!Promise} Resolves when the caller may make an attempt, possibly
* after a delay. Rejects if no more attempts are allowed.
*/
async attempt() {
if (this.numAttempts_ >= this.maxAttempts_) {
if (this.autoReset_) {
this.reset_();
} else {
throw new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.PLAYER,
shaka.util.Error.Code.ATTEMPTS_EXHAUSTED);
}
}
const currentAttempt = this.numAttempts_;
this.numAttempts_++;
if (currentAttempt == 0) {
goog.asserts.assert(!this.autoReset_, 'Failed to delay with auto-reset!');
return;
}
// We've already tried before, so delay the Promise.
// Fuzz the delay to avoid tons of clients hitting the server at once
// after it recovers from whatever is causing it to fail.
const fuzzedDelayMs = shaka.net.Backoff.fuzz_(
this.nextUnfuzzedDelay_, this.fuzzFactor_);
await new Promise((resolve) => {
shaka.net.Backoff.defer(fuzzedDelayMs, resolve);
});
// Update delay_ for next time.
this.nextUnfuzzedDelay_ *= this.backoffFactor_;
}
/**
* Gets a copy of the default retry parameters.
*
* @return {shaka.extern.RetryParameters}
*/
static defaultRetryParameters() {
// Use a function rather than a constant member so the calling code can
// modify the values without affecting other call results.
return {
maxAttempts: 2,
baseDelay: 1000,
backoffFactor: 2,
fuzzFactor: 0.5,
timeout: 0,
};
}
/**
* Fuzz the input value by +/- fuzzFactor. For example, a fuzzFactor of 0.5
* will create a random value that is between 50% and 150% of the input value.
*
* @param {number} value
* @param {number} fuzzFactor
* @return {number} The fuzzed value
* @private
*/
static fuzz_(value, fuzzFactor) {
// A random number between -1 and +1.
const negToPosOne = (Math.random() * 2.0) - 1.0;
// A random number between -fuzzFactor and +fuzzFactor.
const negToPosFuzzFactor = negToPosOne * fuzzFactor;
// The original value, fuzzed by +/- fuzzFactor.
return value * (1.0 + negToPosFuzzFactor);
}
/**
* Reset state in autoReset mode.
* @private
*/
reset_() {
goog.asserts.assert(this.autoReset_, 'Should only be used for auto-reset!');
this.numAttempts_ = 1;
this.nextUnfuzzedDelay_ = this.baseDelay_;
}
/**
* This method is only public for testing. It allows us to intercept the
* time-delay call.
*
* @param {number} delayInMs
* @param {function()} callback
*/
static defer(delayInMs, callback) {
const timer = new shaka.util.Timer(callback);
timer.tickAfter(delayInMs / 1000);
}
};