Skip to content

Commit

Permalink
Exponential Backoff Followup (#136)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions <github-actions@github.com>
  • Loading branch information
ric-evans and github-actions authored Aug 22, 2023
1 parent d7da944 commit eae7160
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 36 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/wipac-cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ jobs:
pip install .[tests]
- name: Run Tests
run: |
python -m pytest tests --tb=short --log-level=INFO -vvv
python -m pytest tests --tb=short --log-level=DEBUG -vvv
pycycle --here --verbose
py-tests-telemetry:
Expand All @@ -97,7 +97,7 @@ jobs:
pip install .[telemetry,tests]
- name: Run Tests
run: |
python -m pytest tests --tb=short --log-level=INFO
python -m pytest tests --tb=short --log-level=DEBUG
pycycle --here --verbose
- name: Run Integration Test
run: |
Expand Down
8 changes: 4 additions & 4 deletions dependencies-dev.log
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ iniconfig==2.0.0
# via pytest
mccabe==0.7.0
# via flake8
mypy==1.4.1
mypy==1.5.1
# via wipac-rest-tools (setup.py)
mypy-extensions==1.0.0
# via mypy
Expand Down Expand Up @@ -65,11 +65,11 @@ requests-futures==1.0.1
# via wipac-rest-tools (setup.py)
requests-mock==1.11.0
# via wipac-rest-tools (setup.py)
ruff==0.0.282
ruff==0.0.285
# via wipac-rest-tools (setup.py)
six==1.16.0
# via requests-mock
tornado==6.3.2
tornado==6.3.3
# via wipac-rest-tools (setup.py)
types-requests==2.31.0.2
# via wipac-rest-tools (setup.py)
Expand All @@ -82,7 +82,7 @@ typing-extensions==4.7.1
# wipac-dev-tools
urllib3==2.0.4
# via requests
wheel==0.41.1
wheel==0.41.2
# via wipac-rest-tools (setup.py)
wipac-dev-tools==1.6.16
# via wipac-rest-tools (setup.py)
16 changes: 8 additions & 8 deletions dependencies-mypy.log
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ cffi==1.15.1
# via cryptography
charset-normalizer==3.2.0
# via requests
click==8.1.6
click==8.1.7
# via
# click-completion
# pycycle
Expand All @@ -24,7 +24,7 @@ colorama==0.4.6
# via crayons
coloredlogs==15.0.1
# via wipac-telemetry
coverage[toml]==7.2.7
coverage[toml]==7.3.0
# via
# pytest-cov
# wipac-rest-tools (setup.py)
Expand All @@ -44,7 +44,7 @@ googleapis-common-protos==1.56.2
# via
# opentelemetry-exporter-jaeger-proto-grpc
# opentelemetry-exporter-otlp-proto-http
grpcio==1.56.2
grpcio==1.57.0
# via opentelemetry-exporter-jaeger-proto-grpc
httpretty==1.1.4
# via wipac-rest-tools (setup.py)
Expand All @@ -62,7 +62,7 @@ markupsafe==2.1.3
# via jinja2
mccabe==0.7.0
# via flake8
mypy==1.4.1
mypy==1.5.1
# via wipac-rest-tools (setup.py)
mypy-extensions==1.0.0
# via mypy
Expand Down Expand Up @@ -142,9 +142,9 @@ requests-futures==1.0.1
# via wipac-rest-tools (setup.py)
requests-mock==1.11.0
# via wipac-rest-tools (setup.py)
ruff==0.0.282
ruff==0.0.285
# via wipac-rest-tools (setup.py)
shellingham==1.5.0.post1
shellingham==1.5.3
# via click-completion
six==1.16.0
# via
Expand All @@ -153,7 +153,7 @@ six==1.16.0
# thrift
thrift==0.16.0
# via opentelemetry-exporter-jaeger-thrift
tornado==6.3.2
tornado==6.3.3
# via wipac-rest-tools (setup.py)
types-requests==2.31.0.2
# via wipac-rest-tools (setup.py)
Expand All @@ -168,7 +168,7 @@ typing-extensions==4.7.1
# wipac-telemetry
urllib3==2.0.4
# via requests
wheel==0.41.1
wheel==0.41.2
# via wipac-rest-tools (setup.py)
wipac-dev-tools==1.6.16
# via
Expand Down
4 changes: 2 additions & 2 deletions dependencies-telemetry.log
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ googleapis-common-protos==1.56.2
# via
# opentelemetry-exporter-jaeger-proto-grpc
# opentelemetry-exporter-otlp-proto-http
grpcio==1.56.2
grpcio==1.57.0
# via opentelemetry-exporter-jaeger-proto-grpc
humanfriendly==10.0
# via coloredlogs
Expand Down Expand Up @@ -88,7 +88,7 @@ six==1.16.0
# via thrift
thrift==0.16.0
# via opentelemetry-exporter-jaeger-thrift
tornado==6.3.2
tornado==6.3.3
# via wipac-rest-tools (setup.py)
typing-extensions==4.7.1
# via
Expand Down
10 changes: 5 additions & 5 deletions dependencies-tests.log
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ cffi==1.15.1
# via cryptography
charset-normalizer==3.2.0
# via requests
click==8.1.6
click==8.1.7
# via
# click-completion
# pycycle
click-completion==0.5.2
# via pycycle
colorama==0.4.6
# via crayons
coverage[toml]==7.2.7
coverage[toml]==7.3.0
# via
# pytest-cov
# wipac-rest-tools (setup.py)
Expand Down Expand Up @@ -83,15 +83,15 @@ requests-futures==1.0.1
# via wipac-rest-tools (setup.py)
requests-mock==1.11.0
# via wipac-rest-tools (setup.py)
ruff==0.0.282
ruff==0.0.285
# via wipac-rest-tools (setup.py)
shellingham==1.5.0.post1
shellingham==1.5.3
# via click-completion
six==1.16.0
# via
# click-completion
# requests-mock
tornado==6.3.2
tornado==6.3.3
# via wipac-rest-tools (setup.py)
types-requests==2.31.0.2
# via wipac-rest-tools (setup.py)
Expand Down
2 changes: 1 addition & 1 deletion dependencies.log
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ requests==2.31.0
# wipac-rest-tools (setup.py)
requests-futures==1.0.1
# via wipac-rest-tools (setup.py)
tornado==6.3.2
tornado==6.3.3
# via wipac-rest-tools (setup.py)
typing-extensions==4.7.1
# via
Expand Down
46 changes: 34 additions & 12 deletions rest_tools/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@

import jwt
import requests
import urllib3

from .. import telemetry as wtt
from ..utils.json_util import JSONType, json_decode
from .session import AsyncSession, Session

MAX_RETRIES = 15
MAX_RETRIES = 30


def _to_str(s: Union[str, bytes]) -> str:
Expand All @@ -42,6 +43,13 @@ class CalcRetryFromBackoffMax:

backoff_max: float

def __post_init__(self) -> None:
if self.backoff_max > urllib3.util.retry.Retry.DEFAULT_BACKOFF_MAX:
raise ValueError(
f"CalcRetryFromBackoffMax.backoff_max ({self.backoff_max})"
f" cannot be greater than: {urllib3.util.retry.Retry.DEFAULT_BACKOFF_MAX=}"
)

def calculate_retries(self, backoff_factor: float) -> int:
"""Calculate the number of retries."""
# backoff_last = backoff_factor * 2^(retries-1)
Expand Down Expand Up @@ -71,18 +79,26 @@ def calculate_retries(self, timeout: float, backoff_factor: float) -> int:
)

# the first backoff is always 0 sec, factor applies after 2nd attempt
# T + 0 + sum{n=1,retries-1}(T + 2^n * B) + T
# = T + 0 + ( T*k + B*2^((retries-1)+1) - 2*B ) + T
# not easily solvable (for k) in a closed form
for k in range(0, MAX_RETRIES + 2): # last val -> MAX_RETRIES+1
if self.waittime_max > (
# T + 0 + sum{n=1,retries-1}(T + min[MAX, 2^n * B] ) + T
# sum has no closed form due to `min` function
for candidate in range(0, MAX_RETRIES + 2): # last val is MAX_RETRIES+1
total = (
timeout
+ (timeout * k)
+ (backoff_factor * 2 ** (k))
- (2 * backoff_factor)
+ 0
+ sum(
timeout
+ min(
urllib3.util.retry.Retry.DEFAULT_BACKOFF_MAX,
2**n * backoff_factor,
)
for n in range(1, candidate) # 1 to retries-1 (inclusive)
)
+ timeout
):
retries = k # gets overwritten each iteration
)
# print(candidate)
# print(total)
if self.waittime_max > total:
retries = candidate # gets overwritten each iteration
else:
break
return retries
Expand All @@ -94,8 +110,14 @@ def _log_retries_values(
logger.debug(f"using {retries=} {timeout=} {backoff_factor=}")
if retries:
retries_schema = ' '.join(
# first backoff is 0
[f'<0.0s> {timeout}s']
+ [f'<{(backoff_factor * 2**i)}s> {timeout}s' for i in range(1, retries)]
+
# calculate increasing backoff values
[
f'<{min(urllib3.util.retry.Retry.DEFAULT_BACKOFF_MAX,backoff_factor * 2**i)}s> {timeout}s'
for i in range(1, retries)
]
)
logger.debug(
f"retry scheme (TIMEOUT [<BACKOFF> TIMEOUT ...]): "
Expand Down
27 changes: 25 additions & 2 deletions tests/unit_client/client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from unittest.mock import Mock

import pytest
import urllib3
from httpretty import HTTPretty, httprettified # type: ignore[import]
from requests import PreparedRequest
from requests.exceptions import SSLError, Timeout
Expand Down Expand Up @@ -153,9 +154,13 @@ async def test_041_request_autocalc_retries_error() -> None:
for timeout, backoff_factor, arg in [
(0.5, 0.75, MAX_RETRIES + 1),
#
(0.5, 0.001, CalcRetryFromBackoffMax(1000)),
(
0.5,
01e-07,
CalcRetryFromBackoffMax(urllib3.util.retry.Retry.DEFAULT_BACKOFF_MAX),
),
#
(0.5, 0.001, CalcRetryFromWaittimeMax(1000)),
(0.5, 0.3, CalcRetryFromWaittimeMax(60 * 60)),
]:
print(f"{timeout=}, {backoff_factor=}, {arg=}")
with pytest.raises(
Expand All @@ -170,6 +175,24 @@ async def test_041_request_autocalc_retries_error() -> None:
)


@pytest.mark.asyncio
async def test_042_request_autocalc_retries_error() -> None:
"""Test auto-calculated retries options in `RestClient`."""
with pytest.raises(
ValueError,
match=r"CalcRetryFromBackoffMax\.backoff_max \(\d+\) cannot be greater than: urllib3\.util\.retry\.Retry\.DEFAULT_BACKOFF_MAX=\d+",
):
RestClient(
"http://test",
"passkey",
timeout=0.5,
retries=CalcRetryFromBackoffMax(
urllib3.util.retry.Retry.DEFAULT_BACKOFF_MAX + 1
),
backoff_factor=0.001,
)


def test_100_request_seq(requests_mock: Mock) -> None:
"""Test `request_seq()`."""
result = {"result": "the result"}
Expand Down

0 comments on commit eae7160

Please sign in to comment.