mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 03:16:37 +00:00
Pin the DNS walk against real urllib3 types, state the git.py divergence
This commit is contained in:
@@ -38,11 +38,15 @@ def _is_permanent_dns_failure(e: BaseException) -> bool:
|
||||
if id(exc) in seen:
|
||||
continue
|
||||
if isinstance(exc, socket.gaierror):
|
||||
# Everything except EAI_AGAIN is treated as permanent. Codes
|
||||
# like EAI_NONAME can occur during a brief network outage too,
|
||||
# but a 6s backoff rarely outlives one, and permanent means
|
||||
# callers fall back to their cache immediately instead of
|
||||
# sleeping first (the offline-build case).
|
||||
# Everything except EAI_AGAIN is treated as permanent. This is
|
||||
# deliberately narrower than git.py, which retries NXDOMAIN
|
||||
# too: codes like EAI_NONAME can occur during a brief network
|
||||
# outage, but a 6s backoff rarely outlives one, and permanent
|
||||
# means callers with a cached copy fall back to it immediately
|
||||
# instead of sleeping first (the offline-build case). The
|
||||
# trade-off is that a hard resolver failure on a first
|
||||
# download fails without retrying, same as before retries
|
||||
# existed.
|
||||
return exc.errno != socket.EAI_AGAIN
|
||||
seen.add(id(exc))
|
||||
stack.extend(
|
||||
|
||||
@@ -43,18 +43,23 @@ class TestIsTransientDownloadError:
|
||||
offline builds must fall back to their cache without sleeping first.
|
||||
requests can surface the gaierror via the cause chain or via
|
||||
urllib3's MaxRetryError.reason attribute."""
|
||||
from urllib3.exceptions import MaxRetryError, NameResolutionError
|
||||
|
||||
gai = socket.gaierror(socket.EAI_NONAME, "nodename nor servname provided")
|
||||
|
||||
chained = req.ConnectionError("resolution failed")
|
||||
chained.__cause__ = gai
|
||||
assert not is_transient_download_error(chained)
|
||||
|
||||
class _FakeMaxRetryError(Exception):
|
||||
def __init__(self, reason: BaseException) -> None:
|
||||
super().__init__("max retries exceeded")
|
||||
self.reason = reason
|
||||
|
||||
wrapped = req.ConnectionError(_FakeMaxRetryError(gai))
|
||||
# The real urllib3 shape, raised the way urllib3 raises it: the
|
||||
# gaierror is NameResolutionError.__cause__ (set at the raise
|
||||
# site), which MaxRetryError carries in its reason attribute.
|
||||
try:
|
||||
raise NameResolutionError("example.invalid", None, gai) from gai
|
||||
except NameResolutionError as nre:
|
||||
wrapped = req.ConnectionError(
|
||||
MaxRetryError(None, "http://example.invalid/", reason=nre)
|
||||
)
|
||||
assert not is_transient_download_error(wrapped)
|
||||
|
||||
# A garden-variety connection reset stays transient.
|
||||
|
||||
Reference in New Issue
Block a user