Skip to content
mager-bench1.3

GPT-6 Astra × async-fetch

7.3

Async concurrency patterns + retry/timeout handling

correctness 9.0quality 8.0documentation 5.031507ms
$ cat async-fetch.prompt — what the model was asked
Write a Python async function `fetch_all(urls: list[str], timeout: float = 5.0, retries: int = 2) -> list[dict]` that fetches all URLs concurrently using `aiohttp` and returns a list of result dicts.

Each result dict must have:
  - `url`: the original URL
  - `status`: HTTP status code (int), or None on error
  - `body`: response text (str), or None on error
  - `error`: error message string, or None on success

Requirements:
1. All URLs must be fetched concurrently (not sequentially)
2. Each fetch must respect the `timeout` (per-request, not total)
3. Retry up to `retries` times on network errors or 5xx responses (exponential backoff: 0.5s, 1s, ...)
4. Never raise — errors go into the result dict
5. Type-hint everything; add a docstring
6. Show a `__main__` block that fetches 3 URLs and prints results

run 1 of 1

7.3
correctness 9.0quality 8.0documentation 5.031507ms

# judge: Concurrency, per-attempt timeouts, retry/backoff behavior, shared session use, and result ordering are strong, but cancellation is improperly swallowed, unlimited connector concurrency is risky, and the docstring does not explicitly document every parameter, timeout units, or all result keys.

$ cat codex-cli/gpt-6-astra.async-fetch.r1.response
```python
import asyncio
from typing import TypeAlias

import aiohttp

Result: TypeAlias = dict[str, str | int | None]


async def fetch_all(
    urls: list[str],
    timeout: float = 5.0,
    retries: int = 2,
) -> list[Result]:
    """Fetch URLs concurrently, preserving input order.

    Each attempt has its own timeout. Network errors, timeouts, and HTTP
    5xx responses receive up to `retries` additional attempts, separated
    by exponential backoff starting at 0.5 seconds. HTTP 4xx responses
    are returned normally. Failed fetches have null status and body.
    Errors, including cancellation, are returned rather than raised.
    """

    def failure(url: str, exc: BaseException) -> Result:
        return {
            "url": url,
            "status": None,
            "body": None,
            "error": f"{type(exc).__name__}: {exc}",
        }

    async def fetch_one(session: aiohttp.ClientSession, url: str) -> Result:
        try:
            for attempt in range(retries + 1):
                try:
                    async with session.get(
                        url, timeout=aiohttp.ClientTimeout(total=timeout)
                    ) as response:
                        if 500 <= response.status < 600:
                            # Exit the response context before backing off.
                            error: Exception = RuntimeError(
                                f"HTTP {response.status}: {response.reason}"
                            )
                        else:
                            body: str = await response.text()
                            return {
                                "url": url,
                                "status": response.status,
                                "body": body,
                                "error": None,
                            }
                except (aiohttp.ClientConnectionError,
                        aiohttp.ClientPayloadError,
                        asyncio.TimeoutError) as exc:
                    error = exc

                if attempt == retries:
                    return failure(url, error)

                await asyncio.sleep(0.5 * (2 ** attempt))

        except (Exception, asyncio.CancelledError) as exc:
            return failure(url, exc)

        return failure(url, RuntimeError("No request attempts were made"))

    try:
        if timeout <= 0:
            raise ValueError("timeout must be positive")
        if retries < 0:
            raise ValueError("retries must be nonnegative")

        # Remove the connector's default concurrency limit.
        async with aiohttp.ClientSession(
            connector=aiohttp.TCPConnector(limit=0)
        ) as session:
            return list(
                await asyncio.gather(*(fetch_one(session, url) for url in urls))
            )
    except (Exception, asyncio.CancelledError) as exc:
        return [failure(url, exc) for url in urls]


async def main() -> None:
    results: list[Result] = await fetch_all(
        [
            "https://example.com",
            "https://www.python.org",
            "https://httpbin.org/status/503",
        ]
    )
    for result in results:
        print(result)


if __name__ == "__main__":
    asyncio.run(main())
```