diff --git a/CONTRIBUTORS b/CONTRIBUTORS index c2f001351e..6a92450750 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -30,3 +30,4 @@ Sanborn Hilland Timothy Drews Vasanth Polipelli Vignesh Venkatasubramanian +Roi Lipman \ No newline at end of file diff --git a/lib/util/ajax_request.js b/lib/util/ajax_request.js index 100e893456..7b76cdaf45 100644 --- a/lib/util/ajax_request.js +++ b/lib/util/ajax_request.js @@ -196,6 +196,7 @@ shaka.util.AjaxRequest.prototype.cleanupRequest_ = function() { this.xhr_.onload = null; this.xhr_.onreadystatechange = null; this.xhr_.onerror = null; + this.xhr_.ontimeout = null; } this.xhr_ = null; }; @@ -254,6 +255,7 @@ shaka.util.AjaxRequest.prototype.sendInternal = function() { this.xhr_.onreadystatechange = this.onReadyStateChange_.bind(this); } this.xhr_.onerror = this.onError_.bind(this); + this.xhr_.ontimeout = this.onTimeout_.bind(this); for (var k in this.parameters.requestHeaders) { this.xhr_.setRequestHeader(k, this.parameters.requestHeaders[k]); @@ -480,3 +482,34 @@ shaka.util.AjaxRequest.prototype.onError_ = function(event) { this.destroy_(); }; +/** + * Handles a "timeout" event. + * + * @param {!ProgressEvent} event The ProgressEvent from this.xhr_. + * + * @private + */ +shaka.util.AjaxRequest.prototype.onTimeout_ = function(event) { + if (this.attempts_ < this.parameters.maxAttempts) { + this.cleanupRequest_(); + + var sendAgain = this.sendInternal.bind(this); + + // Fuzz the delay to avoid tons of clients hitting the server at once + // after it recovers from whatever is causing it to fail. + var negToPosOne = (Math.random() * 2.0) - 1.0; + var negToPosFuzzFactor = negToPosOne * this.parameters.retryFuzzFactor; + var fuzzedDelay = this.retryDelayMs_ * (1.0 + negToPosFuzzFactor); + window.setTimeout(sendAgain, fuzzedDelay); + + // Store the fuzzed delay to make testing retries feasible. + this.lastDelayMs_ = fuzzedDelay; + + // Back off the next delay. + this.retryDelayMs_ *= this.parameters.retryBackoffFactor; + } else { + var error = this.createError_('Request timmedout.', 'net'); + this.promise_.reject(error); + this.destroy_(); + } +}