From 6a03d0b1b49862ccd529cc6d1283a5094a69887c Mon Sep 17 00:00:00 2001 From: Iurii Pliner Date: Wed, 8 Jan 2025 18:09:44 +0000 Subject: [PATCH 1/5] Limit deadline split between attempts by a factor --- aio_request/deadline_provider.py | 18 ++++++++------ tests/test_deadline_provider.py | 40 ++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/aio_request/deadline_provider.py b/aio_request/deadline_provider.py index 8ba53eb..0facf64 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(split_factor: int | None = None) -> DeadlineProvider: """ Split deadline between attempts. @@ -18,15 +18,19 @@ def split_deadline_between_attempts() -> DeadlineProvider: the last one has received the remaining 8 seconds due to redistribution. """ + if split_factor is not None and split_factor < 2: + raise ValueError("max_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 split_factor is None: + effective_split_factor = attempts_count - attempt + else: + effective_split_factor = min(split_factor, attempts_count) - attempt + if effective_split_factor <= 1: + return deadline + return deadline / effective_split_factor return __provider diff --git a/tests/test_deadline_provider.py b/tests/test_deadline_provider.py index c583fbb..9fcefbd 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(split_factor=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(split_factor=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 From 36f45cdcf3ebce0f96c7daf967635fc09792686d Mon Sep 17 00:00:00 2001 From: Iurii Pliner Date: Wed, 8 Jan 2025 18:44:25 +0000 Subject: [PATCH 2/5] Add to CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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) From 2940de97e5b3417039fd9c738c56f754c6d21457 Mon Sep 17 00:00:00 2001 From: Iurii Pliner Date: Wed, 8 Jan 2025 19:22:07 +0000 Subject: [PATCH 3/5] Fix --- aio_request/deadline_provider.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aio_request/deadline_provider.py b/aio_request/deadline_provider.py index 0facf64..ab6c499 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(split_factor: int | None = None) -> DeadlineProvider: +def split_deadline_between_attempts(*, split_factor: int | None = None) -> DeadlineProvider: """ Split deadline between attempts. @@ -19,7 +19,7 @@ def split_deadline_between_attempts(split_factor: int | None = None) -> Deadline """ if split_factor is not None and split_factor < 2: - raise ValueError("max_split should be greater or equal to 2") + raise ValueError("split_factor should be greater or equal to 2") def __provider(deadline: Deadline, attempt: int, attempts_count: int) -> Deadline: if deadline.expired: From f1859feb162aef7104f423c687415368f28ff408 Mon Sep 17 00:00:00 2001 From: Iurii Pliner Date: Wed, 8 Jan 2025 19:26:33 +0000 Subject: [PATCH 4/5] Fix review comments --- aio_request/deadline_provider.py | 16 ++++++++-------- tests/test_deadline_provider.py | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/aio_request/deadline_provider.py b/aio_request/deadline_provider.py index ab6c499..c21e6a6 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(*, split_factor: int | None = None) -> DeadlineProvider: +def split_deadline_between_attempts(*, attempts_count_to_split: int | None = None) -> DeadlineProvider: """ Split deadline between attempts. @@ -18,19 +18,19 @@ def split_deadline_between_attempts(*, split_factor: int | None = None) -> Deadl the last one has received the remaining 8 seconds due to redistribution. """ - if split_factor is not None and split_factor < 2: - raise ValueError("split_factor should be greater or equal to 2") + 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 - if split_factor is None: - effective_split_factor = attempts_count - attempt + if attempts_count_to_split is None: + effective_attempts_left = attempts_count - attempt else: - effective_split_factor = min(split_factor, attempts_count) - attempt - if effective_split_factor <= 1: + effective_attempts_left = min(attempts_count_to_split, attempts_count) - attempt + if effective_attempts_left <= 1: return deadline - return deadline / effective_split_factor + return deadline / effective_attempts_left return __provider diff --git a/tests/test_deadline_provider.py b/tests/test_deadline_provider.py index 9fcefbd..01de8c0 100644 --- a/tests/test_deadline_provider.py +++ b/tests/test_deadline_provider.py @@ -21,7 +21,7 @@ async def test_split_deadline_between_attempt(): async def test_split_deadline_between_attempt_with_split_factor(): - provider = aio_request.split_deadline_between_attempts(split_factor=2) + 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) @@ -56,7 +56,7 @@ async def test_split_deadline_between_attempts_fast_attempt_failure(): async def test_split_deadline_between_attempts_fast_attempt_failure_with_split_factor(): - provider = aio_request.split_deadline_between_attempts(split_factor=2) + 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) From 990ef738a4b5a3b33845977ae572246cb49be1c8 Mon Sep 17 00:00:00 2001 From: Iurii Pliner Date: Wed, 8 Jan 2025 19:29:17 +0000 Subject: [PATCH 5/5] Add more docs --- aio_request/deadline_provider.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aio_request/deadline_provider.py b/aio_request/deadline_provider.py index c21e6a6..5c4cc94 100644 --- a/aio_request/deadline_provider.py +++ b/aio_request/deadline_provider.py @@ -16,6 +16,8 @@ def split_deadline_between_attempts(*, attempts_count_to_split: int | None = Non 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: