Files
shaka-player/lib/net/backoff.js
T
Jacob Trimble 624acc66b8 Add curly braces to all blocks.
Google style guide requires adding curly braces to all block statements
even if it is only has one line.  This fixes it by using eslint's
--fix flag followed by running clang-format to reformat the change.

Change-Id: Idc086c2aa8c02df5ef8b2140a11bfb9128eeb4bd
2018-02-21 11:23:34 -08:00

226 lines
6.6 KiB
JavaScript

/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
goog.provide('shaka.net.Backoff');
goog.require('goog.asserts');
goog.require('shaka.util.PublicPromise');
/**
* Backoff represents delay and backoff state. This is used by NetworkingEngine
* for individual requests and by StreamingEngine to retry streaming failures.
*
* @param {shakaExtern.RetryParameters} parameters
* @param {boolean=} opt_autoReset If true, start at a "first retry" state and
* and auto-reset that state when we reach maxAttempts.
* @param {?function()=} opt_isCanceled If provided, the backoff will end the
* current attempt early when this callback returns true.
*
* @struct
* @constructor
*/
shaka.net.Backoff = function(parameters, opt_autoReset, opt_isCanceled) {
// Set defaults as we unpack these, so that individual app-level requests in
// NetworkingEngine can be missing parameters.
let 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_ = opt_autoReset || false;
/** @private {?function()} */
this.isCanceled_ = opt_isCanceled || null;
if (this.autoReset_) {
// There is no delay before the first attempt. In StreamingEngine (consumer
// 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.
*/
shaka.net.Backoff.prototype.attempt = function() {
if (this.numAttempts_ >= this.maxAttempts_) {
if (this.autoReset_) {
this.reset_();
} else {
return Promise.reject();
}
}
let p = new shaka.util.PublicPromise();
if (this.numAttempts_) {
// 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.
let fuzzedDelay =
shaka.net.Backoff.fuzz_(this.nextUnfuzzedDelay_, this.fuzzFactor_);
this.cancelableTimeout_(p.resolve, fuzzedDelay);
// Update delay_ for next time.
this.nextUnfuzzedDelay_ *= this.backoffFactor_;
} else {
goog.asserts.assert(!this.autoReset_, 'Failed to delay with auto-reset!');
p.resolve();
}
this.numAttempts_++;
return p;
};
/**
* Gets a copy of the default retry parameters.
*
* @return {shakaExtern.RetryParameters}
*/
shaka.net.Backoff.defaultRetryParameters = function() {
// 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
*/
shaka.net.Backoff.fuzz_ = function(value, fuzzFactor) {
// A random number between -1 and +1
let negToPosOne = (Math.random() * 2.0) - 1.0;
// A random number between -fuzzFactor and +fuzzFactor
let negToPosFuzzFactor = negToPosOne * fuzzFactor;
// The original value, fuzzed by +/- fuzzFactor
return value * (1.0 + negToPosFuzzFactor);
};
/**
* Reset state in autoReset mode.
* @private
*/
shaka.net.Backoff.prototype.reset_ = function() {
goog.asserts.assert(this.autoReset_, 'Should only be used for auto-reset!');
this.numAttempts_ = 1;
this.nextUnfuzzedDelay_ = this.baseDelay_;
};
/**
* Makes a timeout that cancels with isCanceled_ if this has an isCanceled_.
*
* @param {Function} fn The callback to invoke when the timeout expires.
* @param {number} timeoutMs The timeout in milliseconds.
* @private
*/
shaka.net.Backoff.prototype.cancelableTimeout_ = function(fn, timeoutMs) {
if (this.isCanceled_) {
if (this.isCanceled_() || timeoutMs == 0) {
fn();
} else {
// This will break the timeout into 200 ms intervals, so that isCanceled_
// will be checked periodically.
let timeToUse = Math.min(200, timeoutMs);
shaka.net.Backoff.setTimeout_(function() {
this.cancelableTimeout_(fn, timeoutMs - timeToUse);
}.bind(this), timeToUse);
}
} else {
shaka.net.Backoff.setTimeout_(fn, timeoutMs);
}
};
/**
* This is here only for testability. Mocking global setTimeout can lead to
* unintended interactions with other tests. So instead, we mock this.
*
* @param {Function} fn The callback to invoke when the timeout expires.
* @param {number} timeoutMs The timeout in milliseconds.
* @return {number} The timeout ID.
* @private
*/
shaka.net.Backoff.setTimeout_ = function(fn, timeoutMs) {
return window.setTimeout(fn, timeoutMs);
};