michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // This implements logic for stopping requests if the server starts to return michael@0: // too many errors. If we get MAX_ERRORS errors in ERROR_PERIOD minutes, we michael@0: // back off for TIMEOUT_INCREMENT minutes. If we get another error michael@0: // immediately after we restart, we double the timeout and add michael@0: // TIMEOUT_INCREMENT minutes, etc. michael@0: // michael@0: // This is similar to the logic used by the search suggestion service. michael@0: michael@0: // HTTP responses that count as an error. We also include any 5xx response michael@0: // as an error. michael@0: const HTTP_FOUND = 302; michael@0: const HTTP_SEE_OTHER = 303; michael@0: const HTTP_TEMPORARY_REDIRECT = 307; michael@0: michael@0: /** michael@0: * @param maxErrors Number of times to request before backing off. michael@0: * @param retryIncrement Time (ms) for each retry before backing off. michael@0: * @param maxRequests Number the number of requests needed to trigger backoff michael@0: * @param requestPeriod Number time (ms) in which maxRequests have to occur to michael@0: * trigger the backoff behavior michael@0: * @param timeoutIncrement Number time (ms) the starting timeout period michael@0: * we double this time for consecutive errors michael@0: * @param maxTimeout Number time (ms) maximum timeout period michael@0: */ michael@0: function RequestBackoff(maxErrors, retryIncrement, michael@0: maxRequests, requestPeriod, michael@0: timeoutIncrement, maxTimeout) { michael@0: this.MAX_ERRORS_ = maxErrors; michael@0: this.RETRY_INCREMENT_ = retryIncrement; michael@0: this.MAX_REQUESTS_ = maxRequests; michael@0: this.REQUEST_PERIOD_ = requestPeriod; michael@0: this.TIMEOUT_INCREMENT_ = timeoutIncrement; michael@0: this.MAX_TIMEOUT_ = maxTimeout; michael@0: michael@0: // Queue of ints keeping the time of all requests michael@0: this.requestTimes_ = []; michael@0: michael@0: this.numErrors_ = 0; michael@0: this.errorTimeout_ = 0; michael@0: this.nextRequestTime_ = 0; michael@0: } michael@0: michael@0: /** michael@0: * Reset the object for reuse. michael@0: */ michael@0: RequestBackoff.prototype.reset = function() { michael@0: this.numErrors_ = 0; michael@0: this.errorTimeout_ = 0; michael@0: this.nextRequestTime_ = 0; michael@0: } michael@0: michael@0: /** michael@0: * Check to see if we can make a request. michael@0: */ michael@0: RequestBackoff.prototype.canMakeRequest = function() { michael@0: var now = Date.now(); michael@0: if (now < this.nextRequestTime_) { michael@0: return false; michael@0: } michael@0: michael@0: return (this.requestTimes_.length < this.MAX_REQUESTS_ || michael@0: (now - this.requestTimes_[0]) > this.REQUEST_PERIOD_); michael@0: } michael@0: michael@0: RequestBackoff.prototype.noteRequest = function() { michael@0: var now = Date.now(); michael@0: this.requestTimes_.push(now); michael@0: michael@0: // We only care about keeping track of MAX_REQUESTS michael@0: if (this.requestTimes_.length > this.MAX_REQUESTS_) michael@0: this.requestTimes_.shift(); michael@0: } michael@0: michael@0: RequestBackoff.prototype.nextRequestDelay = function() { michael@0: return Math.max(0, this.nextRequestTime_ - Date.now()); michael@0: } michael@0: michael@0: /** michael@0: * Notify this object of the last server response. If it's an error, michael@0: */ michael@0: RequestBackoff.prototype.noteServerResponse = function(status) { michael@0: if (this.isErrorStatus(status)) { michael@0: this.numErrors_++; michael@0: michael@0: if (this.numErrors_ < this.MAX_ERRORS_) michael@0: this.errorTimeout_ = this.RETRY_INCREMENT_; michael@0: else if (this.numErrors_ == this.MAX_ERRORS_) michael@0: this.errorTimeout_ = this.TIMEOUT_INCREMENT_; michael@0: else michael@0: this.errorTimeout_ *= 2; michael@0: michael@0: this.errorTimeout_ = Math.min(this.errorTimeout_, this.MAX_TIMEOUT_); michael@0: this.nextRequestTime_ = Date.now() + this.errorTimeout_; michael@0: } else { michael@0: // Reset error timeout, allow requests to go through. michael@0: this.reset(); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * We consider 302, 303, 307, 4xx, and 5xx http responses to be errors. michael@0: * @param status Number http status michael@0: * @return Boolean true if we consider this http status an error michael@0: */ michael@0: RequestBackoff.prototype.isErrorStatus = function(status) { michael@0: return ((400 <= status && status <= 599) || michael@0: HTTP_FOUND == status || michael@0: HTTP_SEE_OTHER == status || michael@0: HTTP_TEMPORARY_REDIRECT == status); michael@0: } michael@0: