From 5a01bd9fbb314478781515764f61e34413b79591 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Thu, 26 Oct 2023 17:44:50 +0100 Subject: [PATCH] renaming --- src/py/flwr/common/backoff.py | 37 +++++++++++++++++++++--------- src/py/flwr/common/backoff_test.py | 27 +++++++++++++++------- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/py/flwr/common/backoff.py b/src/py/flwr/common/backoff.py index 8aa50100b792..3121ec41f663 100644 --- a/src/py/flwr/common/backoff.py +++ b/src/py/flwr/common/backoff.py @@ -19,6 +19,7 @@ import random import time from dataclasses import dataclass +from logging import WARNING from typing import ( Any, Callable, @@ -31,6 +32,8 @@ Union, ) +from flwr.common.logger import log + def exponential( base_delay: float = 1, @@ -102,9 +105,9 @@ def full_jitter(value: float) -> float: @dataclass class Details: - """Details for event handlers in SafeInvoker.""" + """Details for event handlers in Retrier.""" - func: Callable[..., Any] + target: Callable[..., Any] args: Tuple[Any, ...] kwargs: Dict[str, Any] tries: int @@ -114,7 +117,7 @@ class Details: # pylint: disable-next=too-many-instance-attributes -class SafeInvoker: +class Retrier: """Wrapper class for retry (with backoff) triggered by exceptions. Parameters @@ -123,8 +126,8 @@ class SafeInvoker: A generator yielding successive wait times in seconds. If the generator is finite, the giveup event will be triggered when the generator raises `StopIteration`. - exception: Union[Type[Exception], Iterable[Type[Exception]]] - An exception type (or iterable of types) that triggers backoff. + exception: Union[Type[Exception], Tuple[Type[Exception]]] + An exception type (or tuple of types) that triggers backoff. max_tries: Optional[int] (default: None) The maximum number of attempts to make before giving up. Once exhausted, the exception will be allowed to escape. If set to None, there is no limit @@ -157,9 +160,9 @@ class SafeInvoker: Examples -------- - Initialize a `SafeInvoker` with exponential backoff and call a function: + Initialize a `Retrier` with exponential backoff and call a function: - >>> invoker = SafeInvoker( + >>> invoker = Retrier( >>> exponential(), >>> grpc.RpcError, >>> max_tries=3, @@ -170,7 +173,7 @@ class SafeInvoker: def __init__( self, wait: Generator[float, None, None], - exception: Union[Type[Exception], Iterable[Type[Exception]]], + exception: Union[Type[Exception], Tuple[Type[Exception], ...]], *, max_tries: Optional[int] = None, max_time: Optional[float] = None, @@ -189,10 +192,16 @@ def __init__( self.on_success = on_success self.on_backoff = on_backoff self.on_giveup = on_giveup + if max_tries is None and max_time is None: + log( + WARNING, + "Neither 'max_tries' nor 'max_time' is set. This might result in " + "the Retrier continuously retrying without any limit.", + ) def invoke( self, - func: Callable[..., Any], + target: Callable[..., Any], *args: Tuple[Any, ...], **kwargs: Dict[str, Any], ) -> Any: @@ -232,6 +241,12 @@ def invoke( exceptions that trigger a retry, as well as conditions to stop retries, are also determined by the class's initialization parameters. """ + if self.max_tries is None and self.max_time is None: + log( + WARNING, + "Neither 'max_tries' nor 'max_time' is set. This might result in " + "the Retrier continuously retrying without any limit.", + ) def try_call_event_handler( handler: Optional[Callable[[Details], None]], details: Details @@ -246,11 +261,11 @@ def try_call_event_handler( tries += 1 elapsed = time.time() - start details = Details( - func=func, args=args, kwargs=kwargs, tries=tries, elapsed=elapsed + target=target, args=args, kwargs=kwargs, tries=tries, elapsed=elapsed ) try: - ret = func(*args, **kwargs) + ret = target(*args, **kwargs) except self.exception as err: # type: ignore # Check if giveup event should be triggered max_tries_exceeded = tries == self.max_tries diff --git a/src/py/flwr/common/backoff_test.py b/src/py/flwr/common/backoff_test.py index e82699758287..4168f140425f 100644 --- a/src/py/flwr/common/backoff_test.py +++ b/src/py/flwr/common/backoff_test.py @@ -20,7 +20,7 @@ import pytest -from flwr.common.backoff import SafeInvoker, constant +from flwr.common.backoff import Retrier, constant # Assuming SafeInvoker and related utilities have been imported here... @@ -55,7 +55,7 @@ def test_successful_invocation() -> None: success_handler = Mock() backoff_handler = Mock() giveup_handler = Mock() - invoker = SafeInvoker( + invoker = Retrier( constant(0.1), ValueError, on_success=success_handler, @@ -77,18 +77,29 @@ def test_failure() -> None: """Check termination when unexpected exception is raised.""" # Prepare # `constant([0.1])` generator will raise `StopIteration` after one iteration. - invoker = SafeInvoker(constant(0.1), TypeError) + invoker = Retrier(constant(0.1), TypeError) # Execute and Assert with pytest.raises(ValueError): invoker.invoke(failing_function) +def test_failure_two_exceptions(mock_sleep: MagicMock) -> None: + """Verify one retry on a specified iterable of exceptions.""" + # Prepare + invoker = Retrier(constant(0.1), (TypeError, ValueError), max_tries=2, jitter=None) + + # Execute and Assert + with pytest.raises(ValueError): + invoker.invoke(failing_function) + mock_sleep.assert_called_once_with(0.1) + + def test_backoff_on_failure(mock_sleep: MagicMock) -> None: """Verify one retry on specified exception.""" # Prepare # `constant([0.1])` generator will raise `StopIteration` after one iteration. - invoker = SafeInvoker(constant([0.1]), ValueError, jitter=None) + invoker = Retrier(constant([0.1]), ValueError, jitter=None) # Execute and Assert with pytest.raises(ValueError): @@ -100,7 +111,7 @@ def test_max_tries(mock_sleep: MagicMock) -> None: """Check termination after `max_tries`.""" # Prepare # Disable `jitter` to ensure 0.1s wait time. - invoker = SafeInvoker(constant(0.1), ValueError, max_tries=2, jitter=None) + invoker = Retrier(constant(0.1), ValueError, max_tries=2, jitter=None) # Execute and Assert with pytest.raises(ValueError): @@ -117,7 +128,7 @@ def test_max_time(mock_time: MagicMock, mock_sleep: MagicMock) -> None: 0.0, 3.0, ] - invoker = SafeInvoker(constant(2), ValueError, max_time=2.5) + invoker = Retrier(constant(2), ValueError, max_time=2.5) # Execute and Assert with pytest.raises(ValueError): @@ -132,7 +143,7 @@ def test_event_handlers() -> None: success_handler = Mock() backoff_handler = Mock() giveup_handler = Mock() - invoker = SafeInvoker( + invoker = Retrier( constant(0.1), ValueError, max_tries=2, @@ -156,7 +167,7 @@ def test_giveup_condition() -> None: def should_give_up(exc: Exception) -> bool: return isinstance(exc, ValueError) - invoker = SafeInvoker(constant(0.1), ValueError, giveup_condition=should_give_up) + invoker = Retrier(constant(0.1), ValueError, giveup_condition=should_give_up) # Execute and Assert with pytest.raises(ValueError):