Skip to content

Commit

Permalink
renaming
Browse files Browse the repository at this point in the history
  • Loading branch information
panh99 committed Oct 26, 2023
1 parent c92992b commit 5a01bd9
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 19 deletions.
37 changes: 26 additions & 11 deletions src/py/flwr/common/backoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import random
import time
from dataclasses import dataclass
from logging import WARNING
from typing import (
Any,
Callable,
Expand All @@ -31,6 +32,8 @@
Union,
)

from flwr.common.logger import log


def exponential(
base_delay: float = 1,
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
27 changes: 19 additions & 8 deletions src/py/flwr/common/backoff_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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...

Expand Down Expand Up @@ -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,
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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,
Expand All @@ -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):
Expand Down

0 comments on commit 5a01bd9

Please sign in to comment.