From 55e05bcb1e8fd703d76539dc6c74a0345fb926c7 Mon Sep 17 00:00:00 2001 From: Jason2866 Date: Sun, 12 Apr 2026 22:08:52 +0200 Subject: [PATCH 1/2] retry when transient HTTP errors 502/503 occur --- platform.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/platform.py b/platform.py index 50c60dbd4..2f976a2c9 100644 --- a/platform.py +++ b/platform.py @@ -46,6 +46,7 @@ import shutil import struct import subprocess +import time from pathlib import Path from typing import Optional, Dict, List, Any, Union @@ -139,6 +140,47 @@ def is_internet_available(): """ return has_internet_connection() + +def patch_file_downloader(): + """Monkey-patch PlatformIO's FileDownloader to retry on transient HTTP errors (502/503).""" + from platformio.package.download import FileDownloader + from platformio.package.exception import PackageException + + if getattr(FileDownloader.__init__, "_patched", False): + return + + original_init = FileDownloader.__init__ + + def patched_init(self, *args, **kwargs): + max_retries = 5 + for attempt in range(max_retries): + try: + original_init(self, *args, **kwargs) + return + except PackageException as e: + if attempt < max_retries - 1: + delay = 2 ** (attempt + 1) + logger.warning( + "Package download failed: %s. Retrying in %ds... (attempt %d/%d)", + e, delay, attempt + 1, max_retries, + ) + try: + if hasattr(self, "_http_response") and self._http_response is not None: + self._http_response.close() + if hasattr(self, "_http_session"): + self._http_session.close() + except Exception: + pass + time.sleep(delay) + else: + raise + + patched_init._patched = True + FileDownloader.__init__ = patched_init + + +patch_file_downloader() + def safe_file_operation(operation_func): """Decorator for safe filesystem operations with error handling.""" def wrapper(*args, **kwargs): @@ -571,14 +613,26 @@ def _configure_arduino_framework(self, frameworks: List[str], mcu: str) -> None: self.packages["framework-arduinoespressif32"]["optional"] = False self.packages["framework-arduinoespressif32-libs"]["optional"] = False if is_internet_available(): - try: - response = requests.get(ARDUINO_ESP32_PACKAGE_URL, timeout=30) - response.raise_for_status() - packjdata = response.json() - dyn_lib_url = packjdata['packages'][0]['tools'][0]['systems'][0]['url'] - self.packages["framework-arduinoespressif32-libs"]["version"] = dyn_lib_url - except (requests.RequestException, KeyError, IndexError) as e: - logger.error(f"Failed to fetch Arduino framework library URL: {e}") + max_retries = 5 + for attempt in range(max_retries): + try: + response = requests.get(ARDUINO_ESP32_PACKAGE_URL, timeout=30) + response.raise_for_status() + packjdata = response.json() + dyn_lib_url = packjdata['packages'][0]['tools'][0]['systems'][0]['url'] + self.packages["framework-arduinoespressif32-libs"]["version"] = dyn_lib_url + break + except (requests.RequestException, KeyError, IndexError) as e: + if attempt < max_retries - 1: + delay = 2 ** (attempt + 1) + logger.warning( + "Failed to fetch Arduino framework library URL: %s. " + "Retrying in %ds... (attempt %d/%d)", + e, delay, attempt + 1, max_retries, + ) + time.sleep(delay) + else: + logger.error(f"Failed to fetch Arduino framework library URL: {e}") if mcu == "esp32c2": self.packages["framework-arduino-c2-skeleton-lib"]["optional"] = False if mcu == "esp32c61": From 713853601eafc6b93e8ca19623b4b74fc652f408 Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Sun, 12 Apr 2026 22:52:40 +0200 Subject: [PATCH 2/2] Enhance error handling in patch_file_downloader Updated exception handling in patch_file_downloader to include ValueError. Modified docstring to clarify retry on transient HTTP errors. --- platform.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platform.py b/platform.py index 2f976a2c9..b29f036bc 100644 --- a/platform.py +++ b/platform.py @@ -142,7 +142,7 @@ def is_internet_available(): def patch_file_downloader(): - """Monkey-patch PlatformIO's FileDownloader to retry on transient HTTP errors (502/503).""" + """Monkey-patch PlatformIO's FileDownloader to retry on transient HTTP errors.""" from platformio.package.download import FileDownloader from platformio.package.exception import PackageException @@ -169,8 +169,8 @@ def patched_init(self, *args, **kwargs): self._http_response.close() if hasattr(self, "_http_session"): self._http_session.close() - except Exception: - pass + except (AttributeError, OSError) as cleanup_err: + logger.debug("Retry cleanup failed: %s", cleanup_err) time.sleep(delay) else: raise @@ -622,7 +622,7 @@ def _configure_arduino_framework(self, frameworks: List[str], mcu: str) -> None: dyn_lib_url = packjdata['packages'][0]['tools'][0]['systems'][0]['url'] self.packages["framework-arduinoespressif32-libs"]["version"] = dyn_lib_url break - except (requests.RequestException, KeyError, IndexError) as e: + except (requests.RequestException, ValueError, KeyError, IndexError) as e: if attempt < max_retries - 1: delay = 2 ** (attempt + 1) logger.warning(