fix: drop session-default sock_read that poisons pooled keep-alive connections

Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
2026-07-03 19:05:51 -04:00
parent a7302a6356
commit 78dfba0962
3 changed files with 24 additions and 5 deletions
+17 -3
View File
@@ -11,18 +11,18 @@ and swap the HTTP client while inheriting everything else.
`requirements.txt`: `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: Direct:
```bash ```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). 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 ## Usage
@@ -149,6 +149,20 @@ Two changes can't be shimmed without re-introducing the bugs they fix:
## Changelog ## 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 ### v0.1.12
- **Docstring-only.** Restored one-line docstrings on `FailureResponse`'s - **Docstring-only.** Restored one-line docstrings on `FailureResponse`'s
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "aioweb" 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." description = "Async HTTP session wrapper over aiohttp — proxies, header overwrites, retries, previews. Config-free, installable."
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = [
+6 -1
View File
@@ -92,12 +92,17 @@ class ExtendedSession:
`headers` isn't baked in here - aiohttp would copy it into an immutable map `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 update_headers/clear_headers can't touch; `_default_headers` is the mutable
layer request()/preview() merge per call instead. 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( return aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout( timeout=aiohttp.ClientTimeout(
total=timeout, total=timeout,
connect=timeout / 2, connect=timeout / 2,
sock_read=timeout / 2,
sock_connect=timeout / 2, sock_connect=timeout / 2,
), ),
**kwargs, **kwargs,