{"record":{"id":"91b0071959ece52a","repo":"dotnet/efcore","slug":"failed-to-download-url-after-max-retries-attem","errorCode":null,"errorMessage":"Failed to download {url} after {max_retries} attempts.","messagePattern":"Failed to download (.+?) after (.+?) attempts\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"eng/common/cross/install-debs.py","lineNumber":47,"sourceCode":"\n                        # verify checksum if provided\n                        if checksum:\n                            sha256 = hashlib.sha256(content).hexdigest()\n                            if sha256 != checksum:\n                                raise Exception(f\"SHA256 mismatch for {url}: expected {checksum}, got {sha256}\")\n\n                        f.write(content)\n                    print(f\"Downloaded {url} at {dest_path}\")\n                    return\n                else:\n                    raise Exception(f\"Failed to download {url}, Status Code: {response.status}\")\n        except (asyncio.CancelledError, asyncio.TimeoutError, aiohttp.ClientError) as e:\n            print(f\"Error downloading {url}: {type(e).__name__} - {e}. Retrying...\")\n\n        attempt += 1\n        await asyncio.sleep(retry_delay)\n\n    raise Exception(f\"Failed to download {url} after {max_retries} attempts.\")\n\nasync def download_deb_files_parallel(mirror, packages, tmp_dir):\n    \"\"\"Download .deb files in parallel.\"\"\"\n    os.makedirs(tmp_dir, exist_ok=True)\n\n    tasks = []\n    timeout = aiohttp.ClientTimeout(total=60)\n    async with aiohttp.ClientSession(timeout=timeout) as session:\n        for pkg, info in packages.items():\n            filename = info.get(\"Filename\")\n            if filename:\n                url = f\"{mirror}/{filename}\"\n                dest_path = os.path.join(tmp_dir, os.path.basename(filename))\n                tasks.append(asyncio.create_task(download_file(session, url, dest_path, checksum=info.get(\"SHA256\"))))\n\n        await asyncio.gather(*tasks)\n\nasync def download_package_index_parallel(mirror, arch, suites, check_sig, keyring):","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/dotnet/efcore/blob/dbf9771522148d61a2467854921bd5dc6f6e6916/eng/common/cross/install-debs.py#L29-L65","documentation":"Raised by download_file after all max_retries attempts (default 3) failed with transient network exceptions - asyncio.CancelledError, asyncio.TimeoutError, or aiohttp.ClientError. Note that an HTTP non-200 raises error 660 immediately and does not count toward these retries; this error is purely for connection/timeout level failures that never produced a usable response.","triggerScenarios":"Every retry attempt of session.get(url) raised a caught network exception: DNS resolution failure, TCP connect timeout, read timeout exceeding the aiohttp.ClientTimeout total, TLS handshake error, or connection reset. After max_retries the loop exits and raises.","commonSituations":"No internet or restricted network; corporate proxy/firewall blocking the mirror host; DNS misconfigured; the per-call timeout (default 60s) too short for a large Packages.gz or .deb over a slow link; mirror host temporarily unreachable.","solutions":["Confirm basic connectivity to the mirror host: `curl -v <mirror>` / `getent hosts <mirror-host>`.","Call download_file with larger timeout/max_retries/retry_delay arguments to tolerate slow links.","Check proxy/ firewall settings and export http_proxy/https_proxy if a corporate proxy is required.","Switch to a closer or more reliable mirror."],"exampleFix":"# before\nawait download_file(session, url, dest_path, checksum=info.get(\"SHA256\"))\n# after\nawait download_file(session, url, dest_path, max_retries=5, retry_delay=5, timeout=180, checksum=info.get(\"SHA256\"))","handlingStrategy":"retry","validationCode":"# Pre-flight reachability of the mirror host before the parallel downloads\nimport socket\n\ndef ensure_reachable(host, port=443):\n    try:\n        socket.gethostbyname(host)\n        with socket.create_connection((host, port), timeout=10):\n            return True\n    except OSError as e:\n        raise RuntimeError(f\"Mirror host {host} unreachable: {e}; check DNS/proxy/firewall\")\n\nfrom urllib.parse import urlparse\nh = urlparse(mirror).hostname\nensure_reachable(h)","typeGuard":null,"tryCatchPattern":"# Distinguish 'exhausted retries' from HTTP non-200 by message\ntry:\n    await download_file(session, url, dest_path, max_retries=5, retry_delay=5, timeout=180, checksum=checksum)\nexcept Exception as e:\n    if \"after\" in str(e) and \"attempts\" in str(e):\n        # transient network failure - fall back to an alternate mirror\n        await download_file(session, alt_url_for(url), dest_path, max_retries=5, timeout=180, checksum=checksum)\n    else:\n        raise","preventionTips":["Pass larger max_retries/timeout for slow links; defaults (3 / 60s) are tight for big indexes.","Export http_proxy/https_proxy in corporate networks so aiohttp can reach the mirror.","Keep an alternate mirror configured so you can fall back instead of aborting."],"tags":["network","retry","timeout","download"],"analyzedSha":"dbf9771522148d61a2467854921bd5dc6f6e6916","analyzedAt":"2026-08-06T20:46:03.226Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}