From 7c538e6ddb3ef2963af18b3bf1fcf06e76d389ec Mon Sep 17 00:00:00 2001 From: Stainless Bot Date: Tue, 24 Oct 2023 15:27:25 +0000 Subject: [PATCH] feat(client): adjust retry behavior to be exponential backoff --- src/finch/_base_client.py | 10 ++++---- tests/test_client.py | 48 +++++++++++++++++++++------------------ 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/finch/_base_client.py b/src/finch/_base_client.py index 44065d1c..8005c8cf 100644 --- a/src/finch/_base_client.py +++ b/src/finch/_base_client.py @@ -158,7 +158,7 @@ class BasePage(GenericModel, Generic[ModelT]): Methods: has_next_page(): Check if there is another page available - next_page_info(): Get the necesary information to make a request for the next page + next_page_info(): Get the necessary information to make a request for the next page """ _options: FinalRequestOptions = PrivateAttr() @@ -691,15 +691,15 @@ def _calculate_retry_timeout( return retry_after initial_retry_delay = 0.5 - max_retry_delay = 2.0 + max_retry_delay = 8.0 nb_retries = max_retries - remaining_retries # Apply exponential backoff, but not more than the max. - sleep_seconds = min(initial_retry_delay * pow(nb_retries - 1, 2), max_retry_delay) + sleep_seconds = min(initial_retry_delay * pow(2.0, nb_retries), max_retry_delay) # Apply some jitter, plus-or-minus half a second. - jitter = random() - 0.5 - timeout = sleep_seconds + jitter + jitter = 1 - 0.25 * random() + timeout = sleep_seconds * jitter return timeout if timeout >= 0 else 0 def _should_retry(self, response: httpx.Response) -> bool: diff --git a/tests/test_client.py b/tests/test_client.py index b5acca48..9b005518 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -639,18 +639,20 @@ class Model(BaseModel): "remaining_retries,retry_after,timeout", [ [3, "20", 20], - [3, "0", 2], - [3, "-10", 2], + [3, "0", 0.5], + [3, "-10", 0.5], [3, "60", 60], - [3, "61", 2], + [3, "61", 0.5], [3, "Fri, 29 Sep 2023 16:26:57 GMT", 20], - [3, "Fri, 29 Sep 2023 16:26:37 GMT", 2], - [3, "Fri, 29 Sep 2023 16:26:27 GMT", 2], + [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5], + [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5], [3, "Fri, 29 Sep 2023 16:27:37 GMT", 60], - [3, "Fri, 29 Sep 2023 16:27:38 GMT", 2], - [3, "99999999999999999999999999999999999", 2], - [3, "Zun, 29 Sep 2023 16:26:27 GMT", 2], - [3, "", 2], + [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5], + [3, "99999999999999999999999999999999999", 0.5], + [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5], + [3, "", 0.5], + [2, "", 0.5 * 2.0], + [1, "", 0.5 * 4.0], ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @@ -658,9 +660,9 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str client = Finch(base_url=base_url, access_token=access_token, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) - options = FinalRequestOptions(method="get", url="/foo", max_retries=2) + options = FinalRequestOptions(method="get", url="/foo", max_retries=3) calculated = client._calculate_retry_timeout(remaining_retries, options, headers) - assert calculated == pytest.approx(timeout, 0.6) # pyright: ignore[reportUnknownMemberType] + assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType] class TestAsyncFinch: @@ -1272,18 +1274,20 @@ class Model(BaseModel): "remaining_retries,retry_after,timeout", [ [3, "20", 20], - [3, "0", 2], - [3, "-10", 2], + [3, "0", 0.5], + [3, "-10", 0.5], [3, "60", 60], - [3, "61", 2], + [3, "61", 0.5], [3, "Fri, 29 Sep 2023 16:26:57 GMT", 20], - [3, "Fri, 29 Sep 2023 16:26:37 GMT", 2], - [3, "Fri, 29 Sep 2023 16:26:27 GMT", 2], + [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5], + [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5], [3, "Fri, 29 Sep 2023 16:27:37 GMT", 60], - [3, "Fri, 29 Sep 2023 16:27:38 GMT", 2], - [3, "99999999999999999999999999999999999", 2], - [3, "Zun, 29 Sep 2023 16:26:27 GMT", 2], - [3, "", 2], + [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5], + [3, "99999999999999999999999999999999999", 0.5], + [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5], + [3, "", 0.5], + [2, "", 0.5 * 2.0], + [1, "", 0.5 * 4.0], ], ) @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 client = AsyncFinch(base_url=base_url, access_token=access_token, _strict_response_validation=True) headers = httpx.Headers({"retry-after": retry_after}) - options = FinalRequestOptions(method="get", url="/foo", max_retries=2) + options = FinalRequestOptions(method="get", url="/foo", max_retries=3) calculated = client._calculate_retry_timeout(remaining_retries, options, headers) - assert calculated == pytest.approx(timeout, 0.6) # pyright: ignore[reportUnknownMemberType] + assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType]