Skip to content

fix(downloader): cap qB httpx keepalive to prevent stale-socket storms (#984)#1028

Merged
EstrellaXD merged 1 commit into
3.2-devfrom
fix/qb-client-connection-limits
Apr 19, 2026
Merged

fix(downloader): cap qB httpx keepalive to prevent stale-socket storms (#984)#1028
EstrellaXD merged 1 commit into
3.2-devfrom
fix/qb-client-connection-limits

Conversation

@EstrellaXD
Copy link
Copy Markdown
Owner

Summary

After roughly an hour of idle, the renamer cycle would flood the log with hundreds of `Server disconnected without sending a response` errors — most commonly reported by users with a proxy between AB and qBittorrent (Orbstack host-network, local 127/8 SOCKS, reverse proxy, NAS). The proxy silently reaps idle TCP sockets while the shared httpx connection pool keeps reusing them.

Same failure pattern as the RSS side, which was fixed in #1018 with `httpx.Limits(keepalive_expiry=...)`. Applying the same recipe to `QbDownloader`.

Change

`backend/src/module/downloader/client/qb_downloader.py`

```python
limits = httpx.Limits(
max_keepalive_connections=5,
max_connections=10,
keepalive_expiry=30.0,
)
self._client = httpx.AsyncClient(timeout=timeout, limits=limits, verify=False)
```

  • `keepalive_expiry=30s` — drops idle sockets before a typical proxy / NAS reap (60-120s)
  • `max_keepalive_connections=5` / `max_connections=10` — caps parallel load on the downloader and anything fronting it, so a renamer iterating over hundreds of torrents can't stampede

Test plan

  • Added `test_auth_sets_connection_limits_for_keepalive` — asserts `limits` is passed with a finite `keepalive_expiry` and `max_connections`
  • All 32 `test_qb_downloader.py` tests green
  • Full suite: 715 passed, 72 skipped, 2 xfailed

Risk

Low. Behaviour change is limited to the pool's keepalive / concurrency ceiling. qB runs on loopback / LAN for most users, so a 10-connection ceiling is more than enough (the renamer runs sequentially per torrent). Users with non-proxied direct qB connections see no visible change because the pool is cycled at 30s idle instead of indefinitely — the only cost is one extra TCP handshake on very sparse traffic.

Closes #984

#984)

After ~1 hour of idle, the next renamer cycle would flood the log with
"Server disconnected without sending a response" errors. This reproduces
most often when AB sits behind a proxy (host-network Orbstack, local 127/8
proxy, remote qB) that silently reaps idle TCP sockets while httpx
continues to reuse them from its connection pool.

Apply the same httpx.Limits recipe used for the shared RSS client in
#1018:
  - keepalive_expiry=30s so the pool drops idle sockets before a proxy /
    NAS does
  - max_keepalive_connections=5 / max_connections=10 so parallel renamer
    calls can't flood the fronting proxy

Closes #984
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
auto-bangumi Ready Ready Preview, Comment Apr 19, 2026 11:09am

@EstrellaXD EstrellaXD merged commit 8169a2c into 3.2-dev Apr 19, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant