|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 // This implements logic for stopping requests if the server starts to return |
|
6 // too many errors. If we get MAX_ERRORS errors in ERROR_PERIOD minutes, we |
|
7 // back off for TIMEOUT_INCREMENT minutes. If we get another error |
|
8 // immediately after we restart, we double the timeout and add |
|
9 // TIMEOUT_INCREMENT minutes, etc. |
|
10 // |
|
11 // This is similar to the logic used by the search suggestion service. |
|
12 |
|
13 // HTTP responses that count as an error. We also include any 5xx response |
|
14 // as an error. |
|
15 const HTTP_FOUND = 302; |
|
16 const HTTP_SEE_OTHER = 303; |
|
17 const HTTP_TEMPORARY_REDIRECT = 307; |
|
18 |
|
19 /** |
|
20 * @param maxErrors Number of times to request before backing off. |
|
21 * @param retryIncrement Time (ms) for each retry before backing off. |
|
22 * @param maxRequests Number the number of requests needed to trigger backoff |
|
23 * @param requestPeriod Number time (ms) in which maxRequests have to occur to |
|
24 * trigger the backoff behavior |
|
25 * @param timeoutIncrement Number time (ms) the starting timeout period |
|
26 * we double this time for consecutive errors |
|
27 * @param maxTimeout Number time (ms) maximum timeout period |
|
28 */ |
|
29 function RequestBackoff(maxErrors, retryIncrement, |
|
30 maxRequests, requestPeriod, |
|
31 timeoutIncrement, maxTimeout) { |
|
32 this.MAX_ERRORS_ = maxErrors; |
|
33 this.RETRY_INCREMENT_ = retryIncrement; |
|
34 this.MAX_REQUESTS_ = maxRequests; |
|
35 this.REQUEST_PERIOD_ = requestPeriod; |
|
36 this.TIMEOUT_INCREMENT_ = timeoutIncrement; |
|
37 this.MAX_TIMEOUT_ = maxTimeout; |
|
38 |
|
39 // Queue of ints keeping the time of all requests |
|
40 this.requestTimes_ = []; |
|
41 |
|
42 this.numErrors_ = 0; |
|
43 this.errorTimeout_ = 0; |
|
44 this.nextRequestTime_ = 0; |
|
45 } |
|
46 |
|
47 /** |
|
48 * Reset the object for reuse. |
|
49 */ |
|
50 RequestBackoff.prototype.reset = function() { |
|
51 this.numErrors_ = 0; |
|
52 this.errorTimeout_ = 0; |
|
53 this.nextRequestTime_ = 0; |
|
54 } |
|
55 |
|
56 /** |
|
57 * Check to see if we can make a request. |
|
58 */ |
|
59 RequestBackoff.prototype.canMakeRequest = function() { |
|
60 var now = Date.now(); |
|
61 if (now < this.nextRequestTime_) { |
|
62 return false; |
|
63 } |
|
64 |
|
65 return (this.requestTimes_.length < this.MAX_REQUESTS_ || |
|
66 (now - this.requestTimes_[0]) > this.REQUEST_PERIOD_); |
|
67 } |
|
68 |
|
69 RequestBackoff.prototype.noteRequest = function() { |
|
70 var now = Date.now(); |
|
71 this.requestTimes_.push(now); |
|
72 |
|
73 // We only care about keeping track of MAX_REQUESTS |
|
74 if (this.requestTimes_.length > this.MAX_REQUESTS_) |
|
75 this.requestTimes_.shift(); |
|
76 } |
|
77 |
|
78 RequestBackoff.prototype.nextRequestDelay = function() { |
|
79 return Math.max(0, this.nextRequestTime_ - Date.now()); |
|
80 } |
|
81 |
|
82 /** |
|
83 * Notify this object of the last server response. If it's an error, |
|
84 */ |
|
85 RequestBackoff.prototype.noteServerResponse = function(status) { |
|
86 if (this.isErrorStatus(status)) { |
|
87 this.numErrors_++; |
|
88 |
|
89 if (this.numErrors_ < this.MAX_ERRORS_) |
|
90 this.errorTimeout_ = this.RETRY_INCREMENT_; |
|
91 else if (this.numErrors_ == this.MAX_ERRORS_) |
|
92 this.errorTimeout_ = this.TIMEOUT_INCREMENT_; |
|
93 else |
|
94 this.errorTimeout_ *= 2; |
|
95 |
|
96 this.errorTimeout_ = Math.min(this.errorTimeout_, this.MAX_TIMEOUT_); |
|
97 this.nextRequestTime_ = Date.now() + this.errorTimeout_; |
|
98 } else { |
|
99 // Reset error timeout, allow requests to go through. |
|
100 this.reset(); |
|
101 } |
|
102 } |
|
103 |
|
104 /** |
|
105 * We consider 302, 303, 307, 4xx, and 5xx http responses to be errors. |
|
106 * @param status Number http status |
|
107 * @return Boolean true if we consider this http status an error |
|
108 */ |
|
109 RequestBackoff.prototype.isErrorStatus = function(status) { |
|
110 return ((400 <= status && status <= 599) || |
|
111 HTTP_FOUND == status || |
|
112 HTTP_SEE_OTHER == status || |
|
113 HTTP_TEMPORARY_REDIRECT == status); |
|
114 } |
|
115 |