Skip to content

Commit 6c76643

Browse files
feat(client): adjust retry behavior to be exponential backoff (#149)
1 parent 7f9db48 commit 6c76643

File tree

2 files changed

+31
-27
lines changed

2 files changed

+31
-27
lines changed

src/finch/_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
@@ -639,28 +639,30 @@ class Model(BaseModel):
639639
"remaining_retries,retry_after,timeout",
640640
[
641641
[3, "20", 20],
642-
[3, "0", 2],
643-
[3, "-10", 2],
642+
[3, "0", 0.5],
643+
[3, "-10", 0.5],
644644
[3, "60", 60],
645-
[3, "61", 2],
645+
[3, "61", 0.5],
646646
[3, "Fri, 29 Sep 2023 16:26:57 GMT", 20],
647-
[3, "Fri, 29 Sep 2023 16:26:37 GMT", 2],
648-
[3, "Fri, 29 Sep 2023 16:26:27 GMT", 2],
647+
[3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5],
648+
[3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5],
649649
[3, "Fri, 29 Sep 2023 16:27:37 GMT", 60],
650-
[3, "Fri, 29 Sep 2023 16:27:38 GMT", 2],
651-
[3, "99999999999999999999999999999999999", 2],
652-
[3, "Zun, 29 Sep 2023 16:26:27 GMT", 2],
653-
[3, "", 2],
650+
[3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5],
651+
[3, "99999999999999999999999999999999999", 0.5],
652+
[3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5],
653+
[3, "", 0.5],
654+
[2, "", 0.5 * 2.0],
655+
[1, "", 0.5 * 4.0],
654656
],
655657
)
656658
@mock.patch("time.time", mock.MagicMock(return_value=1696004797))
657659
def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
658660
client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True)
659661

660662
headers = httpx.Headers({"retry-after": retry_after})
661-
options = FinalRequestOptions(method="get", url="/foo", max_retries=2)
663+
options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
662664
calculated = client._calculate_retry_timeout(remaining_retries, options, headers)
663-
assert calculated == pytest.approx(timeout, 0.6) # pyright: ignore[reportUnknownMemberType]
665+
assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType]
664666

665667

666668
class TestAsyncFinch:
@@ -1272,18 +1274,20 @@ class Model(BaseModel):
12721274
"remaining_retries,retry_after,timeout",
12731275
[
12741276
[3, "20", 20],
1275-
[3, "0", 2],
1276-
[3, "-10", 2],
1277+
[3, "0", 0.5],
1278+
[3, "-10", 0.5],
12771279
[3, "60", 60],
1278-
[3, "61", 2],
1280+
[3, "61", 0.5],
12791281
[3, "Fri, 29 Sep 2023 16:26:57 GMT", 20],
1280-
[3, "Fri, 29 Sep 2023 16:26:37 GMT", 2],
1281-
[3, "Fri, 29 Sep 2023 16:26:27 GMT", 2],
1282+
[3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5],
1283+
[3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5],
12821284
[3, "Fri, 29 Sep 2023 16:27:37 GMT", 60],
1283-
[3, "Fri, 29 Sep 2023 16:27:38 GMT", 2],
1284-
[3, "99999999999999999999999999999999999", 2],
1285-
[3, "Zun, 29 Sep 2023 16:26:27 GMT", 2],
1286-
[3, "", 2],
1285+
[3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5],
1286+
[3, "99999999999999999999999999999999999", 0.5],
1287+
[3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5],
1288+
[3, "", 0.5],
1289+
[2, "", 0.5 * 2.0],
1290+
[1, "", 0.5 * 4.0],
12871291
],
12881292
)
12891293
@mock.patch("time.time", mock.MagicMock(return_value=1696004797))
@@ -1292,6 +1296,6 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte
12921296
client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True)
12931297

12941298
headers = httpx.Headers({"retry-after": retry_after})
1295-
options = FinalRequestOptions(method="get", url="/foo", max_retries=2)
1299+
options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
12961300
calculated = client._calculate_retry_timeout(remaining_retries, options, headers)
1297-
assert calculated == pytest.approx(timeout, 0.6) # pyright: ignore[reportUnknownMemberType]
1301+
assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType]

0 commit comments

Comments
 (0)