DEVX-156: fix: add tenacity retry to install_tools._download for transient network failures
This commit is contained in:
@@ -22,18 +22,37 @@ Usage::
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import tarfile
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
from tenacity import (
|
||||
Retrying,
|
||||
before_sleep_log,
|
||||
retry_if_exception_type,
|
||||
stop_after_attempt,
|
||||
wait_exponential,
|
||||
)
|
||||
|
||||
TARGET_DIR = Path.home() / ".local" / "bin"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Retry configuration for transient network failures during download.
|
||||
# GitHub releases occasionally drops connections ("Remote end closed
|
||||
# connection without response"). Retrying with backoff before falling
|
||||
# through to the next fallback URL makes the build resilient to
|
||||
# momentary network blips.
|
||||
MAX_DOWNLOAD_RETRIES = 3
|
||||
|
||||
ACTIONLINT_VERSION = "1.7.12"
|
||||
|
||||
GIT_CLIFF_VERSION = "2.13.1"
|
||||
@@ -64,8 +83,30 @@ def _ensure_target_dir() -> Path:
|
||||
return TARGET_DIR
|
||||
|
||||
|
||||
def _download(url: str, dest: Path) -> None:
|
||||
"""Download a file from ``url`` to ``dest`` with a 60s timeout."""
|
||||
def _download(url: str, dest: Path, *, _sleep=None) -> None:
|
||||
"""Download a file from ``url`` to ``dest`` with retry and 60s timeout.
|
||||
|
||||
Retries up to ``MAX_DOWNLOAD_RETRIES`` times on transient network
|
||||
errors (``URLError``, ``OSError`` from connection resets) using
|
||||
exponential backoff. This handles momentary GitHub releases
|
||||
connection drops that were causing CI image builds to fail.
|
||||
|
||||
The ``_sleep`` kwarg is for tests to avoid real sleeping; production
|
||||
code should leave it as ``None`` (uses ``time.sleep``).
|
||||
"""
|
||||
retrying = Retrying(
|
||||
stop=stop_after_attempt(MAX_DOWNLOAD_RETRIES),
|
||||
wait=wait_exponential(multiplier=2, min=2, max=10),
|
||||
retry=retry_if_exception_type((urllib.error.URLError, OSError, ConnectionError)),
|
||||
before_sleep=before_sleep_log(logger, logging.WARNING),
|
||||
sleep=_sleep if _sleep is not None else time.sleep,
|
||||
reraise=True,
|
||||
)
|
||||
retrying(_do_download, url, dest)
|
||||
|
||||
|
||||
def _do_download(url: str, dest: Path) -> None:
|
||||
"""Single download attempt — called by :func:`_download` retry wrapper."""
|
||||
with urllib.request.urlopen(url, timeout=60) as resp, open(dest, "wb") as f: # nosec B310
|
||||
shutil.copyfileobj(resp, f)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user