Source: lib/net/backoff.js

  1. /**
  2. * @license
  3. * Copyright 2016 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. goog.provide('shaka.net.Backoff');
  18. goog.require('goog.asserts');
  19. goog.require('shaka.util.PublicPromise');
  20. /**
  21. * Backoff represents delay and backoff state. This is used by NetworkingEngine
  22. * for individual requests and by StreamingEngine to retry streaming failures.
  23. *
  24. * @param {shakaExtern.RetryParameters} parameters
  25. * @param {boolean=} opt_autoReset If true, start at a "first retry" state and
  26. * and auto-reset that state when we reach maxAttempts.
  27. *
  28. * @struct
  29. * @constructor
  30. */
  31. shaka.net.Backoff = function(parameters, opt_autoReset) {
  32. // Set defaults as we unpack these, so that individual app-level requests in
  33. // NetworkingEngine can be missing parameters.
  34. var defaults = shaka.net.Backoff.defaultRetryParameters();
  35. /**
  36. * @const
  37. * @private {number}
  38. */
  39. this.maxAttempts_ = (parameters.maxAttempts == null) ?
  40. defaults.maxAttempts : parameters.maxAttempts;
  41. goog.asserts.assert(this.maxAttempts_ >= 1, 'maxAttempts should be >= 1');
  42. /**
  43. * @const
  44. * @private {number}
  45. */
  46. this.baseDelay_ = (parameters.baseDelay == null) ?
  47. defaults.baseDelay : parameters.baseDelay;
  48. goog.asserts.assert(this.baseDelay_ >= 0, 'baseDelay should be >= 0');
  49. /**
  50. * @const
  51. * @private {number}
  52. */
  53. this.fuzzFactor_ = (parameters.fuzzFactor == null) ?
  54. defaults.fuzzFactor : parameters.fuzzFactor;
  55. goog.asserts.assert(this.fuzzFactor_ >= 0, 'fuzzFactor should be >= 0');
  56. /**
  57. * @const
  58. * @private {number}
  59. */
  60. this.backoffFactor_ = (parameters.backoffFactor == null) ?
  61. defaults.backoffFactor : parameters.backoffFactor;
  62. goog.asserts.assert(this.backoffFactor_ >= 0, 'backoffFactor should be >= 0');
  63. /** @private {number} */
  64. this.numAttempts_ = 0;
  65. /** @private {number} */
  66. this.nextUnfuzzedDelay_ = this.baseDelay_;
  67. /** @private {boolean} */
  68. this.autoReset_ = opt_autoReset || false;
  69. if (this.autoReset_) {
  70. // There is no delay before the first attempt. In StreamingEngine (consumer
  71. // of auto-reset mode), the first attempt was implied, so we reset
  72. // numAttempts to 1. Therefore maxAttempts (which includes the first
  73. // attempt) must be at least 2 for us to see a delay.
  74. goog.asserts.assert(this.maxAttempts_ >= 2,
  75. 'maxAttempts must be >= 2 for autoReset == true');
  76. this.numAttempts_ = 1;
  77. }
  78. };
  79. /**
  80. * @return {!Promise} Resolves when the caller may make an attempt, possibly
  81. * after a delay. Rejects if no more attempts are allowed.
  82. */
  83. shaka.net.Backoff.prototype.attempt = function() {
  84. if (this.numAttempts_ >= this.maxAttempts_) {
  85. if (this.autoReset_) {
  86. this.reset_();
  87. } else {
  88. return Promise.reject();
  89. }
  90. }
  91. var p = new shaka.util.PublicPromise();
  92. if (this.numAttempts_) {
  93. // We've already tried before, so delay the Promise.
  94. // Fuzz the delay to avoid tons of clients hitting the server at once
  95. // after it recovers from whatever is causing it to fail.
  96. var fuzzedDelay =
  97. shaka.net.Backoff.fuzz_(this.nextUnfuzzedDelay_, this.fuzzFactor_);
  98. shaka.net.Backoff.setTimeout_(p.resolve, fuzzedDelay);
  99. // Update delay_ for next time.
  100. this.nextUnfuzzedDelay_ *= this.backoffFactor_;
  101. } else {
  102. goog.asserts.assert(!this.autoReset_, 'Failed to delay with auto-reset!');
  103. p.resolve();
  104. }
  105. this.numAttempts_++;
  106. return p;
  107. };
  108. /**
  109. * Gets a copy of the default retry parameters.
  110. *
  111. * @return {shakaExtern.RetryParameters}
  112. */
  113. shaka.net.Backoff.defaultRetryParameters = function() {
  114. // Use a function rather than a constant member so the calling code can
  115. // modify the values without affecting other call results.
  116. return {
  117. maxAttempts: 2,
  118. baseDelay: 1000,
  119. backoffFactor: 2,
  120. fuzzFactor: 0.5,
  121. timeout: 0
  122. };
  123. };
  124. /**
  125. * Fuzz the input value by +/- fuzzFactor. For example, a fuzzFactor of 0.5
  126. * will create a random value that is between 50% and 150% of the input value.
  127. *
  128. * @param {number} value
  129. * @param {number} fuzzFactor
  130. * @return {number} The fuzzed value
  131. * @private
  132. */
  133. shaka.net.Backoff.fuzz_ = function(value, fuzzFactor) {
  134. // A random number between -1 and +1
  135. var negToPosOne = (Math.random() * 2.0) - 1.0;
  136. // A random number between -fuzzFactor and +fuzzFactor
  137. var negToPosFuzzFactor = negToPosOne * fuzzFactor;
  138. // The original value, fuzzed by +/- fuzzFactor
  139. return value * (1.0 + negToPosFuzzFactor);
  140. };
  141. /**
  142. * Reset state in autoReset mode.
  143. * @private
  144. */
  145. shaka.net.Backoff.prototype.reset_ = function() {
  146. goog.asserts.assert(this.autoReset_, 'Should only be used for auto-reset!');
  147. this.numAttempts_ = 1;
  148. this.nextUnfuzzedDelay_ = this.baseDelay_;
  149. };
  150. /**
  151. * This is here only for testability. Mocking global setTimeout can lead to
  152. * unintended interactions with other tests. So instead, we mock this.
  153. *
  154. * @param {Function} fn The callback to invoke when the timeout expires.
  155. * @param {number} timeoutMs The timeout in milliseconds.
  156. * @return {number} The timeout ID.
  157. * @private
  158. */
  159. shaka.net.Backoff.setTimeout_ = function(fn, timeoutMs) {
  160. return window.setTimeout(fn, timeoutMs);
  161. };