From 78dfba09626c9c44c27910e30b3d8cc3b36fbc92 Mon Sep 17 00:00:00 2001 From: disqualifier Date: Fri, 3 Jul 2026 19:05:51 -0400 Subject: [PATCH] fix: drop session-default sock_read that poisons pooled keep-alive connections Signed-off-by: disqualifier --- README.md | 20 +++++++++++++++++--- pyproject.toml | 2 +- src/aioweb/session.py | 7 ++++++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4672952..8c3c271 100644 --- a/README.md +++ b/README.md @@ -11,18 +11,18 @@ and swap the HTTP client while inheriting everything else. `requirements.txt`: ``` -aioweb @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb.git@v0.1.12 +aioweb @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb.git@v0.1.13 ``` Direct: ```bash -pip install "aioweb @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb.git@v0.1.12" +pip install "aioweb @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb.git@v0.1.13" ``` Requires `aiohttp` and `yarl` (pulled transitively). -Drop the `@v0.1.12` suffix from the line above to install the latest unpinned. +Drop the `@v0.1.13` suffix from the line above to install the latest unpinned. ## Usage @@ -149,6 +149,20 @@ Two changes can't be shimmed without re-introducing the bugs they fix: ## Changelog +### v0.1.13 + +- **Session-default timeout no longer poisons pooled keep-alive connections.** + The default `ClientTimeout` set `sock_read=timeout/2` alongside `total`. Under + `aiohttp>=3.14`, that read timer re-arms on every request dispatched over a + pooled protocol, including idle connections between requests; when it fires + it permanently poisons the pooled connection (`SocketTimeoutError` on the next + use, instantly, without contacting the server) — a real error from the server + (e.g. a 503) could come back as a client-side `FailureResponse(status=0, + reason='timeout')` instead. `sock_read` is now dropped from the session + default; `total` (and `connect`/`sock_connect`) still bound every request, and + the per-call `timeout=N` path (`ClientTimeout(total=N)`, no `sock_read`) was + already unaffected. + ### v0.1.12 - **Docstring-only.** Restored one-line docstrings on `FailureResponse`'s diff --git a/pyproject.toml b/pyproject.toml index bf12df3..2cb08c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "aioweb" -version = "0.1.12" +version = "0.1.13" description = "Async HTTP session wrapper over aiohttp — proxies, header overwrites, retries, previews. Config-free, installable." requires-python = ">=3.10" dependencies = [ diff --git a/src/aioweb/session.py b/src/aioweb/session.py index afc242e..bc0cdf4 100644 --- a/src/aioweb/session.py +++ b/src/aioweb/session.py @@ -92,12 +92,17 @@ class ExtendedSession: `headers` isn't baked in here - aiohttp would copy it into an immutable map update_headers/clear_headers can't touch; `_default_headers` is the mutable layer request()/preview() merge per call instead. + + no `sock_read` here - aiohttp re-arms that timer on every request dispatched + over a pooled protocol, including idle keep-alive connections, and firing it + calls `set_exception(SocketTimeoutError)` on the protocol, permanently + poisoning that pooled connection; the next request on it fails instantly + without contacting the server. `total` still bounds every request overall. """ return aiohttp.ClientSession( timeout=aiohttp.ClientTimeout( total=timeout, connect=timeout / 2, - sock_read=timeout / 2, sock_connect=timeout / 2, ), **kwargs,