Skip to content

Commit 3064c35

Browse files
committed
API: Optimize algorithm of Random Exponential Backoff Class.
1 parent 541df63 commit 3064c35

File tree

1 file changed

+19
-21
lines changed

1 file changed

+19
-21
lines changed

zulip/zulip/__init__.py

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from six.moves import urllib
4242
import logging
4343
import six
44+
import math
4445
from typing import Any, Callable, Dict, Iterable, IO, List, Mapping, Optional, Text, Tuple, Union
4546

4647
__version__ = "0.6.3"
@@ -56,12 +57,13 @@
5657
API_VERSTRING = "v1/"
5758

5859
class CountingBackoff(object):
59-
def __init__(self, maximum_retries=10, timeout_success_equivalent=None):
60-
# type: (int, Optional[float]) -> None
60+
def __init__(self, maximum_retries=10, timeout_success_equivalent=None, delay_cap=10.0):
61+
# type: (int, Optional[float], float) -> None
6162
self.number_of_retries = 0
6263
self.maximum_retries = maximum_retries
6364
self.timeout_success_equivalent = timeout_success_equivalent
6465
self.last_attempt_time = 0.0
66+
self.delay_cap = delay_cap
6567

6668
def keep_going(self):
6769
# type: () -> bool
@@ -93,9 +95,11 @@ def fail(self):
9395
super(RandomExponentialBackoff, self).fail()
9496
# Exponential growth with ratio sqrt(2); compute random delay
9597
# between x and 2x where x is growing exponentially
96-
delay_scale = int(2 ** (self.number_of_retries / 2.0 - 1)) + 1
97-
delay = delay_scale + random.randint(1, delay_scale)
98-
message = "Sleeping for %ss [max %s] before retrying." % (delay, delay_scale * 2)
98+
delay_base = 0.5
99+
delay = random.random() * self.delay_cap
100+
if math.log2(self.delay_cap) > self.number_of_retries:
101+
delay_time = random.random() * min(self.delay_cap, delay_base * (2 ** self.number_of_retries))
102+
message = "Sleeping for %ss before retrying." % (delay,)
99103
try:
100104
logger.warning(message)
101105
except NameError:
@@ -509,13 +513,10 @@ def do_api_query(self, orig_request, url, method="POST",
509513
query_state = {
510514
'had_error_retry': False,
511515
'request': request,
512-
'failures': 0,
513516
} # type: Dict[str, Any]
514517

515518
def error_retry(error_string):
516-
# type: (str) -> bool
517-
if not self.retry_on_errors or query_state["failures"] >= 10:
518-
return False
519+
# type: (str) -> None
519520
if self.verbose:
520521
if not query_state["had_error_retry"]:
521522
sys.stdout.write("zulip API(%s): connection error%s -- retrying." %
@@ -525,14 +526,6 @@ def error_retry(error_string):
525526
sys.stdout.write(".")
526527
sys.stdout.flush()
527528
query_state["request"]["dont_block"] = json.dumps(True)
528-
delay_cap = 10
529-
delay_base = 0.5
530-
delay_time = random.random() * delay_cap
531-
if query_state["failures"] <= 5:
532-
delay_time = random.random() * min(delay_cap, delay_base * (2 ** query_state["failures"]))
533-
time.sleep(delay_time)
534-
query_state["failures"] += 1
535-
return True
536529

537530
def end_error_retry(succeeded):
538531
# type: (bool) -> None
@@ -541,8 +534,8 @@ def end_error_retry(succeeded):
541534
print("Success!")
542535
else:
543536
print("Failed!")
544-
545-
while True:
537+
backoff = RandomExponentialBackoff(timeout_success_equivalent=300)
538+
while backoff.keep_going():
546539
try:
547540
if method == "GET":
548541
kwarg = "params"
@@ -565,7 +558,9 @@ def end_error_retry(succeeded):
565558

566559
# On 50x errors, try again after a short sleep
567560
if str(res.status_code).startswith('5'):
568-
if error_retry(" (server %s)" % (res.status_code,)):
561+
error_retry(" (server %s)" % (res.status_code,))
562+
backoff.fail()
563+
if backoff.keep_going():
569564
continue
570565
# Otherwise fall through and process the python-requests error normally
571566
except (requests.exceptions.Timeout, requests.exceptions.SSLError) as e:
@@ -591,7 +586,9 @@ def end_error_retry(succeeded):
591586
# in an invalid site.
592587
raise UnrecoverableNetworkError('cannot connect to server ' + self.base_url)
593588

594-
if error_retry(""):
589+
error_retry("")
590+
backoff.fail()
591+
if backoff.keep_going():
595592
continue
596593
end_error_retry(False)
597594
return {'msg': "Connection error:\n%s" % traceback.format_exc(),
@@ -615,6 +612,7 @@ def end_error_retry(succeeded):
615612
end_error_retry(False)
616613
return {'msg': "Unexpected error from the server", "result": "http-error",
617614
"status_code": res.status_code}
615+
return {'msg': "Unexpected error from the server", "result": "unexpected-error"}
618616

619617
def call_endpoint(self, url=None, method="POST", request=None,
620618
longpolling=False, files=None, timeout=None):

0 commit comments

Comments
 (0)