Skip to content

Commit 0a421d9

Browse files
committed
feat(client): adjust retry behavior to be exponential backoff (#4)
1 parent 5ffbdc4 commit 0a421d9

File tree

2 files changed

+31
-27
lines changed

2 files changed

+31
-27
lines changed

src/orb/_base_client.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ class BasePage(GenericModel, Generic[ModelT]):
158158
159159
Methods:
160160
has_next_page(): Check if there is another page available
161-
next_page_info(): Get the necesary information to make a request for the next page
161+
next_page_info(): Get the necessary information to make a request for the next page
162162
"""
163163

164164
_options: FinalRequestOptions = PrivateAttr()
@@ -691,15 +691,15 @@ def _calculate_retry_timeout(
691691
return retry_after
692692

693693
initial_retry_delay = 0.5
694-
max_retry_delay = 2.0
694+
max_retry_delay = 8.0
695695
nb_retries = max_retries - remaining_retries
696696

697697
# Apply exponential backoff, but not more than the max.
698-
sleep_seconds = min(initial_retry_delay * pow(nb_retries - 1, 2), max_retry_delay)
698+
sleep_seconds = min(initial_retry_delay * pow(2.0, nb_retries), max_retry_delay)
699699

700700
# Apply some jitter, plus-or-minus half a second.
701-
jitter = random() - 0.5
702-
timeout = sleep_seconds + jitter
701+
jitter = 1 - 0.25 * random()
702+
timeout = sleep_seconds * jitter
703703
return timeout if timeout >= 0 else 0
704704

705705
def _should_retry(self, response: httpx.Response) -> bool:

tests/test_client.py

+26-22
Original file line numberDiff line numberDiff line change
@@ -539,28 +539,30 @@ class Model(BaseModel):
539539
"remaining_retries,retry_after,timeout",
540540
[
541541
[3, "20", 20],
542-
[3, "0", 2],
543-
[3, "-10", 2],
542+
[3, "0", 0.5],
543+
[3, "-10", 0.5],
544544
[3, "60", 60],
545-
[3, "61", 2],
545+
[3, "61", 0.5],
546546
[3, "Fri, 29 Sep 2023 16:26:57 GMT", 20],
547-
[3, "Fri, 29 Sep 2023 16:26:37 GMT", 2],
548-
[3, "Fri, 29 Sep 2023 16:26:27 GMT", 2],
547+
[3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5],
548+
[3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5],
549549
[3, "Fri, 29 Sep 2023 16:27:37 GMT", 60],
550-
[3, "Fri, 29 Sep 2023 16:27:38 GMT", 2],
551-
[3, "99999999999999999999999999999999999", 2],
552-
[3, "Zun, 29 Sep 2023 16:26:27 GMT", 2],
553-
[3, "", 2],
550+
[3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5],
551+
[3, "99999999999999999999999999999999999", 0.5],
552+
[3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5],
553+
[3, "", 0.5],
554+
[2, "", 0.5 * 2.0],
555+
[1, "", 0.5 * 4.0],
554556
],
555557
)
556558
@mock.patch("time.time", mock.MagicMock(return_value=1696004797))
557559
def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
558560
client = Orb(base_url=base_url, api_key=api_key, _strict_response_validation=True)
559561

560562
headers = httpx.Headers({"retry-after": retry_after})
561-
options = FinalRequestOptions(method="get", url="/foo", max_retries=2)
563+
options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
562564
calculated = client._calculate_retry_timeout(remaining_retries, options, headers)
563-
assert calculated == pytest.approx(timeout, 0.6) # pyright: ignore[reportUnknownMemberType]
565+
assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType]
564566

565567

566568
class TestAsyncOrb:
@@ -1083,18 +1085,20 @@ class Model(BaseModel):
10831085
"remaining_retries,retry_after,timeout",
10841086
[
10851087
[3, "20", 20],
1086-
[3, "0", 2],
1087-
[3, "-10", 2],
1088+
[3, "0", 0.5],
1089+
[3, "-10", 0.5],
10881090
[3, "60", 60],
1089-
[3, "61", 2],
1091+
[3, "61", 0.5],
10901092
[3, "Fri, 29 Sep 2023 16:26:57 GMT", 20],
1091-
[3, "Fri, 29 Sep 2023 16:26:37 GMT", 2],
1092-
[3, "Fri, 29 Sep 2023 16:26:27 GMT", 2],
1093+
[3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5],
1094+
[3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5],
10931095
[3, "Fri, 29 Sep 2023 16:27:37 GMT", 60],
1094-
[3, "Fri, 29 Sep 2023 16:27:38 GMT", 2],
1095-
[3, "99999999999999999999999999999999999", 2],
1096-
[3, "Zun, 29 Sep 2023 16:26:27 GMT", 2],
1097-
[3, "", 2],
1096+
[3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5],
1097+
[3, "99999999999999999999999999999999999", 0.5],
1098+
[3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5],
1099+
[3, "", 0.5],
1100+
[2, "", 0.5 * 2.0],
1101+
[1, "", 0.5 * 4.0],
10981102
],
10991103
)
11001104
@mock.patch("time.time", mock.MagicMock(return_value=1696004797))
@@ -1103,6 +1107,6 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte
11031107
client = AsyncOrb(base_url=base_url, api_key=api_key, _strict_response_validation=True)
11041108

11051109
headers = httpx.Headers({"retry-after": retry_after})
1106-
options = FinalRequestOptions(method="get", url="/foo", max_retries=2)
1110+
options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
11071111
calculated = client._calculate_retry_timeout(remaining_retries, options, headers)
1108-
assert calculated == pytest.approx(timeout, 0.6) # pyright: ignore[reportUnknownMemberType]
1112+
assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType]

0 commit comments

Comments
 (0)