diff --git a/CHANGELOG.md b/CHANGELOG.md index a174d9d..8aee628 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * [Do not retry low timeout response](https://github.com/anna-money/aio-request/pull/276) * Refactoring around request enrichers and deprecation of setup_v2. Related PRs: [#277](https://github.com/anna-money/aio-request/pull/277), [#282](https://github.com/anna-money/aio-request/pull/282), [#285](https://github.com/anna-money/aio-request/pull/285) * [Deadline provider for sequential strategy](https://github.com/anna-money/aio-request/pull/284) +* [Limit deadline split between attempts by a factor](https://github.com/anna-money/aio-request/pull/286) ## v0.1.34 (2024-11-05) diff --git a/aio_request/deadline_provider.py b/aio_request/deadline_provider.py index 8ba53eb..5c4cc94 100644 --- a/aio_request/deadline_provider.py +++ b/aio_request/deadline_provider.py @@ -5,7 +5,7 @@ DeadlineProvider = Callable[[Deadline, int, int], Deadline] -def split_deadline_between_attempts() -> DeadlineProvider: +def split_deadline_between_attempts(*, attempts_count_to_split: int | None = None) -> DeadlineProvider: """ Split deadline between attempts. @@ -16,17 +16,23 @@ def split_deadline_between_attempts() -> DeadlineProvider: 2. 1 sec -> 1 sec -> 8 sec. Two attempts have spent 1 seconds each, the last one has received the remaining 8 seconds due to redistribution. + + If attempts_count_to_split is not None, then the deadline will be split between the first attempts_count_to_split. """ + if attempts_count_to_split is not None and attempts_count_to_split < 2: + raise ValueError("attempts_count_to_split should be greater or equal to 2") + def __provider(deadline: Deadline, attempt: int, attempts_count: int) -> Deadline: if deadline.expired: return deadline - - attempts_left = attempts_count - attempt - if attempts_left == 0: - raise ValueError("no attempts left") - - return deadline / attempts_left + if attempts_count_to_split is None: + effective_attempts_left = attempts_count - attempt + else: + effective_attempts_left = min(attempts_count_to_split, attempts_count) - attempt + if effective_attempts_left <= 1: + return deadline + return deadline / effective_attempts_left return __provider diff --git a/tests/test_deadline_provider.py b/tests/test_deadline_provider.py index c583fbb..01de8c0 100644 --- a/tests/test_deadline_provider.py +++ b/tests/test_deadline_provider.py @@ -10,11 +10,29 @@ async def test_split_deadline_between_attempt(): attempt_deadline = provider(deadline, 0, 3) assert 0.3 <= attempt_deadline.timeout <= 0.34 - await asyncio.sleep(attempt_deadline.timeout) + await asyncio.sleep(0.33) attempt_deadline = provider(deadline, 1, 3) assert 0.3 <= attempt_deadline.timeout <= 0.34 - await asyncio.sleep(attempt_deadline.timeout) + await asyncio.sleep(0.33) + + attempt_deadline = provider(deadline, 2, 3) + assert 0.3 <= attempt_deadline.timeout <= 0.34 + + +async def test_split_deadline_between_attempt_with_split_factor(): + provider = aio_request.split_deadline_between_attempts(attempts_count_to_split=2) + deadline = aio_request.Deadline.from_timeout(1) + + attempt_deadline = provider(deadline, 0, 3) + assert 0.45 <= attempt_deadline.timeout <= 0.5 + + await asyncio.sleep(0.33) + + attempt_deadline = provider(deadline, 1, 3) + assert 0.6 <= attempt_deadline.timeout <= 0.67 + + await asyncio.sleep(0.33) attempt_deadline = provider(deadline, 2, 3) assert 0.3 <= attempt_deadline.timeout <= 0.34 @@ -35,3 +53,21 @@ async def test_split_deadline_between_attempts_fast_attempt_failure(): attempt_deadline = provider(deadline, 2, 3) assert 0.75 <= attempt_deadline.timeout <= 0.8 + + +async def test_split_deadline_between_attempts_fast_attempt_failure_with_split_factor(): + provider = aio_request.split_deadline_between_attempts(attempts_count_to_split=2) + deadline = aio_request.Deadline.from_timeout(1) + + attempt_deadline = provider(deadline, 0, 3) + assert 0.45 <= attempt_deadline.timeout <= 0.5 + + await asyncio.sleep(0.1) # fast attempt failure + + attempt_deadline = provider(deadline, 1, 3) + assert 0.85 <= attempt_deadline.timeout <= 0.9 + + await asyncio.sleep(0.1) # fast attempt failure + + attempt_deadline = provider(deadline, 2, 3) + assert 0.75 <= attempt_deadline.timeout <= 0.8