Skip to content

Commit c5ab988

Browse files
authoredJul 15, 2024··
Merge pull request #748 from ceache/fix/retry
fix(core): Proper retry count in KazooRetry
2 parents 273bd56 + 86e69f2 commit c5ab988

File tree

2 files changed

+76
-62
lines changed

2 files changed

+76
-62
lines changed
 

‎kazoo/retry.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,10 @@ def __call__(self, func, *args, **kwargs):
133133
except ConnectionClosedError:
134134
raise
135135
except self.retry_exceptions:
136+
self._attempts += 1
136137
# Note: max_tries == -1 means infinite tries.
137138
if self._attempts == self.max_tries:
138139
raise RetryFailedError("Too many retry attempts")
139-
self._attempts += 1
140140
jitter = random.uniform(
141141
1.0 - self.max_jitter, 1.0 + self.max_jitter
142142
)

‎kazoo/tests/test_retry.py

+75-61
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,103 @@
1-
import unittest
1+
from unittest import mock
22

33
import pytest
44

5+
from kazoo import exceptions as ke
6+
from kazoo import retry as kr
57

6-
class TestRetrySleeper(unittest.TestCase):
7-
def _pass(self):
8-
pass
98

10-
def _fail(self, times=1):
11-
from kazoo.retry import ForceRetryError
9+
def _make_retry(*args, **kwargs):
10+
"""Return a KazooRetry instance with a dummy sleep function."""
11+
12+
def _sleep_func(_time):
13+
pass
1214

13-
scope = dict(times=0)
15+
return kr.KazooRetry(*args, sleep_func=_sleep_func, **kwargs)
1416

15-
def inner():
16-
if scope["times"] >= times:
17-
pass
18-
else:
19-
scope["times"] += 1
20-
raise ForceRetryError("Failed!")
2117

22-
return inner
18+
def _make_try_func(times=1):
19+
"""Returns a function that raises ForceRetryError `times` time before
20+
returning None.
21+
"""
22+
callmock = mock.Mock(
23+
side_effect=[kr.ForceRetryError("Failed!")] * times + [None],
24+
)
25+
return callmock
2326

24-
def _makeOne(self, *args, **kwargs):
25-
from kazoo.retry import KazooRetry
2627

27-
return KazooRetry(*args, **kwargs)
28+
def test_call():
29+
retry = _make_retry(delay=0, max_tries=2)
30+
func = _make_try_func()
31+
retry(func, "foo", bar="baz")
32+
assert func.call_args_list == [
33+
mock.call("foo", bar="baz"),
34+
mock.call("foo", bar="baz"),
35+
]
2836

29-
def test_reset(self):
30-
retry = self._makeOne(delay=0, max_tries=2)
31-
retry(self._fail())
32-
assert retry._attempts == 1
33-
retry.reset()
34-
assert retry._attempts == 0
3537

36-
def test_too_many_tries(self):
37-
from kazoo.retry import RetryFailedError
38+
def test_reset():
39+
retry = _make_retry(delay=0, max_tries=2)
40+
func = _make_try_func()
41+
retry(func)
42+
assert (
43+
func.call_count == retry._attempts + 1 == 2
44+
), "Called 2 times, failed _attempts 1, succeeded 1"
45+
retry.reset()
46+
assert retry._attempts == 0
3847

39-
retry = self._makeOne(delay=0)
40-
with pytest.raises(RetryFailedError):
41-
retry(self._fail(times=999))
42-
assert retry._attempts == 1
4348

44-
def test_maximum_delay(self):
45-
def sleep_func(_time):
46-
pass
49+
def test_too_many_tries():
50+
retry = _make_retry(delay=0, max_tries=10)
51+
func = _make_try_func(times=999)
52+
with pytest.raises(kr.RetryFailedError):
53+
retry(func)
54+
assert (
55+
func.call_count == retry._attempts == 10
56+
), "Called 10 times, failed _attempts 10"
4757

48-
retry = self._makeOne(delay=10, max_tries=100, sleep_func=sleep_func)
49-
retry(self._fail(times=10))
50-
assert retry._cur_delay < 4000
51-
# gevent's sleep function is picky about the type
52-
assert type(retry._cur_delay) == float
5358

54-
def test_copy(self):
55-
def _sleep(t):
56-
return None
59+
def test_maximum_delay():
60+
retry = _make_retry(delay=10, max_tries=100, max_jitter=0)
61+
func = _make_try_func(times=2)
62+
retry(func)
63+
assert func.call_count == 3, "Called 3 times, 2 failed _attemps"
64+
assert retry._cur_delay == 10 * 2**2, "Normal exponential backoff"
65+
retry.reset()
66+
func = _make_try_func(times=10)
67+
retry(func)
68+
assert func.call_count == 11, "Called 11 times, 10 failed _attemps"
69+
assert retry._cur_delay == 60, "Delay capped by maximun"
70+
# gevent's sleep function is picky about the type
71+
assert isinstance(retry._cur_delay, float)
5772

58-
retry = self._makeOne(sleep_func=_sleep)
59-
rcopy = retry.copy()
60-
assert rcopy.sleep_func is _sleep
6173

74+
def test_copy():
75+
retry = _make_retry()
76+
rcopy = retry.copy()
77+
assert rcopy is not retry
78+
assert rcopy.sleep_func is retry.sleep_func
6279

63-
class TestKazooRetry(unittest.TestCase):
64-
def _makeOne(self, **kw):
65-
from kazoo.retry import KazooRetry
6680

67-
return KazooRetry(**kw)
81+
def test_connection_closed():
82+
retry = _make_retry()
6883

69-
def test_connection_closed(self):
70-
from kazoo.exceptions import ConnectionClosedError
84+
def testit():
85+
raise ke.ConnectionClosedError
7186

72-
retry = self._makeOne()
87+
with pytest.raises(ke.ConnectionClosedError):
88+
retry(testit)
7389

74-
def testit():
75-
raise ConnectionClosedError()
7690

77-
with pytest.raises(ConnectionClosedError):
78-
retry(testit)
91+
def test_session_expired():
92+
retry = _make_retry(max_tries=1)
7993

80-
def test_session_expired(self):
81-
from kazoo.exceptions import SessionExpiredError
94+
def testit():
95+
raise ke.SessionExpiredError
8296

83-
retry = self._makeOne(max_tries=1)
97+
with pytest.raises(kr.RetryFailedError):
98+
retry(testit)
8499

85-
def testit():
86-
raise SessionExpiredError()
100+
retry = _make_retry(max_tries=1, ignore_expire=False)
87101

88-
with pytest.raises(Exception):
89-
retry(testit)
102+
with pytest.raises(ke.SessionExpiredError):
103+
retry(testit)

0 commit comments

Comments
 (0)
Please sign in to comment.