Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit deadline split between attempts by a factor #286

Merged
merged 5 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
20 changes: 13 additions & 7 deletions aio_request/deadline_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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

Expand Down
40 changes: 38 additions & 2 deletions tests/test_deadline_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Loading