Skip to content

Commit

Permalink
fix(core): Proper retry count in KazooRetry
Browse files Browse the repository at this point in the history
Make sure the number of attempts matches the `max_retry` parameter.
Add unit tests to that effect.
  • Loading branch information
ceache committed Mar 12, 2024
1 parent 6540c93 commit 0e63e58
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 62 deletions.
2 changes: 1 addition & 1 deletion kazoo/retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ def __call__(self, func, *args, **kwargs):
except ConnectionClosedError:
raise
except self.retry_exceptions:
self._attempts += 1
# Note: max_tries == -1 means infinite tries.
if self._attempts == self.max_tries:
raise RetryFailedError("Too many retry attempts")
self._attempts += 1
jitter = random.uniform(
1.0 - self.max_jitter, 1.0 + self.max_jitter
)
Expand Down
140 changes: 79 additions & 61 deletions kazoo/tests/test_retry.py
Original file line number Diff line number Diff line change
@@ -1,89 +1,107 @@
import unittest

import pytest


class TestRetrySleeper(unittest.TestCase):
def _pass(self):
pass
def _sleep_func(_time):
pass


def _fail(self, times=1):
from kazoo.retry import ForceRetryError
def _makeOne(*args, **kwargs):
from kazoo.retry import KazooRetry

scope = dict(times=0)
return KazooRetry(*args, sleep_func=_sleep_func, **kwargs)

def inner():
if scope["times"] >= times:
pass
else:
scope["times"] += 1
raise ForceRetryError("Failed!")

return inner
_try_counts = 0

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

return KazooRetry(*args, **kwargs)
def _make_try_fun(times=1):
"""Returns a function that raises ForceRetryError `times` time before
returning None."""
from kazoo.retry import ForceRetryError

def test_reset(self):
retry = self._makeOne(delay=0, max_tries=2)
retry(self._fail())
assert retry._attempts == 1
retry.reset()
assert retry._attempts == 0
global _try_counts

def test_too_many_tries(self):
from kazoo.retry import RetryFailedError
_try_counts = 0

retry = self._makeOne(delay=0)
with pytest.raises(RetryFailedError):
retry(self._fail(times=999))
assert retry._attempts == 1
def inner():
global _try_counts

def test_maximum_delay(self):
def sleep_func(_time):
if _try_counts >= times:
pass
else:
_try_counts += 1
raise ForceRetryError("Failed!")

return inner


def test_reset():
global _try_counts

retry = _makeOne(delay=0, max_tries=2)
retry(_make_try_fun())
assert _try_counts == retry._attempts == 1
retry.reset()
assert retry._attempts == 0


def test_too_many_tries():
global _try_counts

from kazoo.retry import RetryFailedError

retry = _makeOne(delay=0, max_tries=10)
with pytest.raises(RetryFailedError):
retry(_make_try_fun(times=999))
assert _try_counts == retry._attempts == 10


def test_maximum_delay():
global _try_counts

retry = _makeOne(delay=10, max_tries=100, max_jitter=0)
retry(_make_try_fun(times=2))
assert _try_counts == 2
assert retry._cur_delay == 10 * 2**2, "Normal exponential backoff"
retry.reset()
retry(_make_try_fun(times=10))
assert _try_counts == 10
assert retry._cur_delay == 60, "Delay capped by maximun"
# gevent's sleep function is picky about the type
assert isinstance(retry._cur_delay, float)

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

def test_copy(self):
def _sleep(t):
return None
def test_copy():
retry = _makeOne()
rcopy = retry.copy()
assert rcopy.sleep_func is retry.sleep_func

retry = self._makeOne(sleep_func=_sleep)
rcopy = retry.copy()
assert rcopy.sleep_func is _sleep

def test_connection_closed():
from kazoo.exceptions import ConnectionClosedError

class TestKazooRetry(unittest.TestCase):
def _makeOne(self, **kw):
from kazoo.retry import KazooRetry
retry = _makeOne()

return KazooRetry(**kw)
def testit():
raise ConnectionClosedError()

def test_connection_closed(self):
from kazoo.exceptions import ConnectionClosedError
with pytest.raises(ConnectionClosedError):
retry(testit)

retry = self._makeOne()

def testit():
raise ConnectionClosedError()
def test_session_expired():
from kazoo.exceptions import SessionExpiredError
from kazoo.retry import RetryFailedError

with pytest.raises(ConnectionClosedError):
retry(testit)
retry = _makeOne(max_tries=1)

def test_session_expired(self):
from kazoo.exceptions import SessionExpiredError
def testit():
raise SessionExpiredError()

retry = self._makeOne(max_tries=1)
with pytest.raises(RetryFailedError):
retry(testit)

def testit():
raise SessionExpiredError()
retry = _makeOne(max_tries=1, ignore_expire=False)

with pytest.raises(Exception):
retry(testit)
with pytest.raises(SessionExpiredError):
retry(testit)

0 comments on commit 0e63e58

Please sign in to comment.