Skip to content

Commit

Permalink
limit nvd to default backoff
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Goodman <[email protected]>
  • Loading branch information
wagoodman committed Nov 26, 2024
1 parent f3930db commit 5b86367
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 2 deletions.
1 change: 0 additions & 1 deletion src/vunnel/providers/nvd/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ def _request(self, url: str, parameters: dict[str, str], headers: dict[str, str]
response = http.get(
url,
self.logger,
backoff_in_seconds=30,
params=payload_str,
headers=headers,
timeout=self.timeout,
Expand Down
14 changes: 13 additions & 1 deletion src/vunnel/utils/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def get( # noqa: PLR0913
backoff_in_seconds: int = 3,
timeout: int = DEFAULT_TIMEOUT,
status_handler: Optional[Callable[[requests.Response], None]] = None, # noqa: UP007 - python 3.9
max_interval: int = 600,
**kwargs: Any,
) -> requests.Response:
"""
Expand Down Expand Up @@ -48,7 +49,7 @@ def get( # noqa: PLR0913
last_exception: Exception | None = None
for attempt in range(retries + 1):
if last_exception:
sleep_interval = backoff_in_seconds * 2 ** (attempt - 1) + random.uniform(0, 1) # noqa: S311
sleep_interval = backoff_sleep_interval(backoff_in_seconds, attempt - 1, max_value=max_interval)
logger.warning(f"will retry in {int(sleep_interval)} seconds...")
time.sleep(sleep_interval)

Expand All @@ -73,3 +74,14 @@ def get( # noqa: PLR0913
logger.error(f"last retry of GET {url} failed with {last_exception}")
raise last_exception
raise Exception("unreachable")


def backoff_sleep_interval(interval: int, attempt: int, max_value: None | int = None, jitter: bool = True) -> float:
# this is an exponential backoff
val = interval * 2**attempt
if max_value and val > max_value:
val = max_value
if jitter:
val += random.uniform(0, 1) # noqa: S311
# explanation of S311 disable: rng is not used cryptographically
return val
29 changes: 29 additions & 0 deletions tests/unit/utils/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,32 @@ def test_it_retries_when_status_handler_raises(
# custom status handler raised the first time it was called,
# so we expect the second mock response to be returned overall
assert result == error_response


@pytest.mark.parametrize(
"interval, jitter, max_value, expected",
[
(
30, # interval
False, # jitter
None, # max_value
[30, 60, 120, 240, 480, 960, 1920, 3840, 7680, 15360, 30720, 61440, 122880, 245760, 491520], # expected
),
(
3, # interval
False, # jitter
1000, # max_value
[3, 6, 12, 24, 48, 96, 192, 384, 768, 1000, 1000, 1000, 1000, 1000, 1000], # expected
),
],
)
def test_backoff_sleep_interval(interval, jitter, max_value, expected):
actual = [
http.backoff_sleep_interval(interval, attempt, jitter=jitter, max_value=max_value) for attempt in range(len(expected))
]

if not jitter:
assert actual == expected
else:
for i, (a, e) in enumerate(zip(actual, expected)):
assert a >= e and a <= e + 1, f"Jittered value out of bounds at attempt {i}: {a} (expected ~{e})"

0 comments on commit 5b86367

Please sign in to comment.