DEVX-156: fix: add tenacity retry to install_tools._download for transient network failures

This commit is contained in:
emil User
2026-08-12 18:15:25 +00:00
parent fc5e4634dd
commit 08f38f3635
2 changed files with 105 additions and 2 deletions
+43 -2
View File
@@ -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)