Description of the bug
zensical build hangs for me after switching my home network to dual-stack. Some inventories that have an AAAA record are unavailable via IPV6, but are available via IPV4. The fault no doubt lies with my ISP (Vodafone UK) for not routing properly, but it nonetheless exposes a potential bug in:
|
with urllib.request.urlopen(req) as resp: # noqa: S310 |
urllib.request.urlopen accepts a timeout keyword that leads to all addresses being tried instead of getting stuck on the first address. In my case this means using also IPV4 addresses after failing on IPV6 ones, but I imagine adding timeout might be helpful in other scenarios too.
To Reproduce
Amusingly Vodafone fixed their routing overnight (some cloudflare prefixes were unreachable for me), but I suppose I'd have to use a mock to demonstrate this properly anyway:
import socket
import threading
import time
import urllib.request
from unittest.mock import patch
URL = "https://docs.python.org/3/objects.inv"
_real_getaddrinfo = socket.getaddrinfo
def _mock_getaddrinfo(host, port, *args, **kwargs):
real = _real_getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)
ipv4 = real[0][4][0]
return [
(socket.AF_INET6, socket.SOCK_STREAM, 6, "", ("2001:db8::1", port, 0, 0)),
(socket.AF_INET, socket.SOCK_STREAM, 6, "", (ipv4, port)),
]
def fetch(timeout: float | None = None) -> None:
label = f"timeout={timeout}s" if timeout else "no timeout"
t0 = time.monotonic()
with patch("socket.getaddrinfo", side_effect=_mock_getaddrinfo):
try:
with urllib.request.urlopen(URL, timeout=timeout) as resp: # noqa: S310
resp.read(64)
print(f" [{label}] OK in {time.monotonic() - t0:.1f}s (fell back to IPv4)")
except Exception as e:
print(f" [{label}] {type(e).__name__} after {time.monotonic() - t0:.1f}s")
print("Bug: no timeout — hangs indefinitely on blackholed AAAA")
t = threading.Thread(target=fetch, daemon=True)
t.start()
t.join(timeout=10)
if t.is_alive():
print(" [no timeout] confirmed still hanging after 10s")
print("\nFix: timeout=5 — IPv6 times out, falls back to A record")
fetch(timeout=5)
Running this give the following output:
Bug: no timeout — hangs indefinitely on blackholed AAAA
[no timeout] confirmed still hanging after 10s
Fix: timeout=5 — IPv6 times out, falls back to A record
[timeout=5s] OK in 5.0s (fell back to IPv4)
Expected behavior
- Inventories should be attempted to be downloaded using all addresses, not just first one.
- Build should not hang if nothing can be downloaded (though I don't know if this causes other issues downstream).
Environment information
- __System__: Linux-6.12.80-x86_64-with-glibc2.42
- __Python__: cpython 3.14.3
- __Environment variables__:
- __Installed packages__:
- `mkdocstrings` v1.0.3
Description of the bug
zensical buildhangs for me after switching my home network to dual-stack. Some inventories that have an AAAA record are unavailable via IPV6, but are available via IPV4. The fault no doubt lies with my ISP (Vodafone UK) for not routing properly, but it nonetheless exposes a potential bug in:mkdocstrings/src/mkdocstrings/_internal/download.py
Line 30 in a0c47b9
urllib.request.urlopenaccepts atimeoutkeyword that leads to all addresses being tried instead of getting stuck on the first address. In my case this means using also IPV4 addresses after failing on IPV6 ones, but I imagine adding timeout might be helpful in other scenarios too.To Reproduce
Amusingly Vodafone fixed their routing overnight (some cloudflare prefixes were unreachable for me), but I suppose I'd have to use a mock to demonstrate this properly anyway:
Running this give the following output:
Expected behavior
Environment information