fix: tell a local DNS outage from a gone host

This commit is contained in:
Abdessamad Derraz committed 2026-10-17 01:47:26 +02:00
1 parent cd4a9143f7
commit 08aa0beb22
2 files changed
+36 -6

No files matched your search

+18 -1
View File
@@ -208,12 +208,29 @@ def _host_is_unresolvable(exc: BaseException) -> bool:
if seen is None:
break
if isinstance(seen, socket.gaierror):
return True
# A resolver that answers nothing at all is the local network:
# every profile would read as gone, and the sweep would report
# nothing to review while it reviewed nothing.
return seen.errno == socket.EAI_NONAME and _resolver_answers()
nested = getattr(seen, "reason", None)
seen = nested if isinstance(nested, BaseException) else seen.__cause__
return False
# A name every forge call depends on: if it does not resolve, the fault is
# local and no host can be declared gone.
RESOLVER_PROBE = "github.com"
def _resolver_answers() -> bool:
"""Whether name resolution works here at all. Replaced in tests."""
try:
socket.getaddrinfo(RESOLVER_PROBE, 443)
except OSError:
return False
return True
def _fetch(url: str, accept_json: bool = False) -> bytes | None:
"""Body of a GET, or None on 404.
+18 -5
View File
@@ -9,6 +9,7 @@ import sys
import tempfile
import socket
import unittest
from unittest import mock
import urllib.error
import urllib.request
from pathlib import Path
@@ -291,14 +292,26 @@ class TestGoneUpstream(unittest.TestCase):
self.assertEqual(self.calls, 1)
def test_unresolvable_host_is_gone_and_not_retried(self):
self._serve(
urllib.error.URLError(socket.gaierror(-2, "Name or service not known"))
)
with self.assertRaises(upstream.GoneError):
upstream._fetch("https://host/x")
self._serve(urllib.error.URLError(
socket.gaierror(socket.EAI_NONAME, "Name or service not known")
))
with mock.patch.object(upstream, "_resolver_answers", return_value=True):
with self.assertRaises(upstream.GoneError):
upstream._fetch("https://host/x")
self.assertEqual(self.calls, 1)
self.assertEqual(self.slept, [])
def test_a_local_resolver_outage_is_not_gone(self):
"""With DNS down here, every forge looked gone and profile_sync
listed nothing to review, exiting 0."""
for errno, resolver in ((socket.EAI_NONAME, False), (socket.EAI_AGAIN, True)):
with self.subTest(errno=errno, resolver=resolver):
self._serve(urllib.error.URLError(socket.gaierror(errno, "no")))
with mock.patch.object(upstream, "_resolver_answers", return_value=resolver):
with self.assertRaises(upstream.UpstreamError) as caught:
upstream._fetch("https://host/x")
self.assertNotIsInstance(caught.exception, upstream.GoneError)
def test_a_gone_error_is_still_an_upstream_error(self):
self._serve(_http_error(451))
with self.assertRaises(upstream.UpstreamError):