dotnet/yarp · error · Exception

Failed to download {url} after {max_retries} attempts.

Error message

Failed to download {url} after {max_retries} attempts.

What it means

This exception is raised at the bottom of download_file after the retry loop exhausts all max_retries (default 3) attempts. It is only reached when every attempt failed with an exception type listed in the except clause: asyncio.CancelledError, asyncio.TimeoutError, or aiohttp.ClientError (connection refused, DNS failure, SSL error, etc.). Note that HTTP non-200 responses raise a different exception (error 103) that bypasses this path entirely because it is not caught by the except clause.

Source

Thrown at eng/common/cross/install-debs.py:47

                        # verify checksum if provided
                        if checksum:
                            sha256 = hashlib.sha256(content).hexdigest()
                            if sha256 != checksum:
                                raise Exception(f"SHA256 mismatch for {url}: expected {checksum}, got {sha256}")

                        f.write(content)
                    print(f"Downloaded {url} at {dest_path}")
                    return
                else:
                    raise Exception(f"Failed to download {url}, Status Code: {response.status}")
        except (asyncio.CancelledError, asyncio.TimeoutError, aiohttp.ClientError) as e:
            print(f"Error downloading {url}: {type(e).__name__} - {e}. Retrying...")

        attempt += 1
        await asyncio.sleep(retry_delay)

    raise Exception(f"Failed to download {url} after {max_retries} attempts.")

async def download_deb_files_parallel(mirror, packages, tmp_dir):
    """Download .deb files in parallel."""
    os.makedirs(tmp_dir, exist_ok=True)

    tasks = []
    timeout = aiohttp.ClientTimeout(total=60)
    async with aiohttp.ClientSession(timeout=timeout) as session:
        for pkg, info in packages.items():
            filename = info.get("Filename")
            if filename:
                url = f"{mirror}/{filename}"
                dest_path = os.path.join(tmp_dir, os.path.basename(filename))
                tasks.append(asyncio.create_task(download_file(session, url, dest_path, checksum=info.get("SHA256"))))

        await asyncio.gather(*tasks)

async def download_package_index_parallel(mirror, arch, suites, check_sig, keyring):

View on GitHub (pinned to bd11867bee)

Solutions

  1. Verify network connectivity to the mirror: run curl -v <mirror-url> or ping the mirror host to confirm reachability.
  2. Increase max_retries and retry_delay in the download_file call (e.g. max_retries=5, retry_delay=5) if the mirror is intermittently available.
  3. Check DNS resolution: ensure the mirror hostname resolves correctly (dig <hostname> or nslookup).
  4. If behind a corporate proxy, set the HTTPS_PROXY / HTTP_PROXY environment variables so aiohttp routes through it.
  5. Increase the per-request timeout (currently 60s) if downloading large .deb files from a slow mirror.
  6. Switch to a faster, closer, or more reliable mirror.

Example fix

# before -- default retry parameters may be too conservative
tasks.append(asyncio.create_task(download_file(session, url, dest_path, checksum=info.get("SHA256"))))

# after -- pass more generous retry parameters for flaky mirrors
tasks.append(asyncio.create_task(
    download_file(session, url, dest_path,
                  max_retries=5, retry_delay=5, timeout=120,
                  checksum=info.get("SHA256"))))
Defensive patterns

Strategy: retry

Validate before calling

# Pre-check network connectivity and DNS before starting downloads.
import socket, urllib.parse
parsed = urllib.parse.urlparse(mirror)
try:
    socket.gethostbyname(parsed.hostname)
    print(f"DNS resolves for {parsed.hostname}")
except socket.gaierror:
    print(f"ERROR: Cannot resolve {parsed.hostname}. Check DNS or mirror URL.")

Try / catch

# Catch the final failure and provide a clear diagnostic with retry guidance
try:
    await asyncio.gather(*tasks)
except Exception as e:
    if 'after' in str(e) and 'attempts' in str(e):
        print(f"All retries exhausted. Possible causes: network down, DNS failure, "
              f"TLS error, or mirror unreachable.")
        print(f"Try: increasing max_retries, checking proxy settings, or switching mirrors.")
    raise

Prevention

When it happens

Trigger: download_file's while loop (lines 23-45) iterates max_retries times (default 3). On each attempt, session.get raises asyncio.TimeoutError (the 60-second timeout fires), aiohttp.ClientConnectorError (DNS resolution failure, connection refused, TLS handshake failure), or asyncio.CancelledError. After all attempts, control falls through to line 47 and raises this final exception.

Common situations: DNS resolution failure for the mirror hostname; the mirror host is unreachable (firewall, network partition, VPN routing issue); TLS/SSL certificate problems causing handshake failures; the default 60-second timeout is too short for a slow mirror or large packages; all connection attempts time out behind a restrictive corporate proxy; the mirror is down or rate-limiting all connections; retry_delay (2 seconds) is too short for the mirror to recover between attempts.

Related errors


AI-assisted analysis of dotnet/yarp@bd11867bee (2026-08-13). Data as JSON: /api/errors/a7e169ff3c499d89. Report an issue: GitHub.