41
41
from six .moves import urllib
42
42
import logging
43
43
import six
44
+ import math
44
45
from typing import Any , Callable , Dict , Iterable , IO , List , Mapping , Optional , Text , Tuple , Union
45
46
46
47
__version__ = "0.6.3"
56
57
API_VERSTRING = "v1/"
57
58
58
59
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
61
62
self .number_of_retries = 0
62
63
self .maximum_retries = maximum_retries
63
64
self .timeout_success_equivalent = timeout_success_equivalent
64
65
self .last_attempt_time = 0.0
66
+ self .delay_cap = delay_cap
65
67
66
68
def keep_going (self ):
67
69
# type: () -> bool
@@ -93,9 +95,11 @@ def fail(self):
93
95
super (RandomExponentialBackoff , self ).fail ()
94
96
# Exponential growth with ratio sqrt(2); compute random delay
95
97
# 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 ,)
99
103
try :
100
104
logger .warning (message )
101
105
except NameError :
@@ -509,13 +513,10 @@ def do_api_query(self, orig_request, url, method="POST",
509
513
query_state = {
510
514
'had_error_retry' : False ,
511
515
'request' : request ,
512
- 'failures' : 0 ,
513
516
} # type: Dict[str, Any]
514
517
515
518
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
519
520
if self .verbose :
520
521
if not query_state ["had_error_retry" ]:
521
522
sys .stdout .write ("zulip API(%s): connection error%s -- retrying." %
@@ -525,14 +526,6 @@ def error_retry(error_string):
525
526
sys .stdout .write ("." )
526
527
sys .stdout .flush ()
527
528
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
536
529
537
530
def end_error_retry (succeeded ):
538
531
# type: (bool) -> None
@@ -541,8 +534,8 @@ def end_error_retry(succeeded):
541
534
print ("Success!" )
542
535
else :
543
536
print ("Failed!" )
544
-
545
- while True :
537
+ backoff = RandomExponentialBackoff ( timeout_success_equivalent = 300 )
538
+ while backoff . keep_going () :
546
539
try :
547
540
if method == "GET" :
548
541
kwarg = "params"
@@ -565,7 +558,9 @@ def end_error_retry(succeeded):
565
558
566
559
# On 50x errors, try again after a short sleep
567
560
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 ():
569
564
continue
570
565
# Otherwise fall through and process the python-requests error normally
571
566
except (requests .exceptions .Timeout , requests .exceptions .SSLError ) as e :
@@ -591,7 +586,9 @@ def end_error_retry(succeeded):
591
586
# in an invalid site.
592
587
raise UnrecoverableNetworkError ('cannot connect to server ' + self .base_url )
593
588
594
- if error_retry ("" ):
589
+ error_retry ("" )
590
+ backoff .fail ()
591
+ if backoff .keep_going ():
595
592
continue
596
593
end_error_retry (False )
597
594
return {'msg' : "Connection error:\n %s" % traceback .format_exc (),
@@ -615,6 +612,7 @@ def end_error_retry(succeeded):
615
612
end_error_retry (False )
616
613
return {'msg' : "Unexpected error from the server" , "result" : "http-error" ,
617
614
"status_code" : res .status_code }
615
+ return {'msg' : "Unexpected error from the server" , "result" : "unexpected-error" }
618
616
619
617
def call_endpoint (self , url = None , method = "POST" , request = None ,
620
618
longpolling = False , files = None , timeout = None ):
0 commit comments