v0.74.0: fix controller->agent connection leak (reuse one agentapi client)
agentClient() built a new agentapi.Client (new bare http.Transport, IdleConnTimeout:0) per call and discarded it without closing idle conns -> one leaked idle ESTABLISHED socket per call to the agent :8443, exhausting the ephemeral port range after ~5 days (EADDRNOTAVAIL). Memoize one shared client via sync.Once; harden Transport (MaxIdleConns/PerHost + IdleConnTimeout 90s). Agent/firewall untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,30 @@
|
||||
## Changelog
|
||||
|
||||
### v0.74.0 — fix the controller→agent connection leak (per-call agentapi client) (2026-06-22)
|
||||
|
||||
**Bugfix — agent local-API socket leak that took down the whole agent-backed feature set after ~5 days.**
|
||||
`Server.agentClient()` built a fresh `agentapi.Client` (hence a fresh bare `http.Transport` with
|
||||
`IdleConnTimeout:0`) on **every** call and discarded it without closing idle connections. The agent's
|
||||
keep-alive left one idle ESTABLISHED socket per call to `192.168.0.162:8443`; these accumulated
|
||||
(~5.8k/day, measured 206 in 47 min) until the ephemeral source-port range for that tuple exhausted →
|
||||
`connect: cannot assign requested address` (EADDRNOTAVAIL), killing storage UI, host-metrics, and
|
||||
whole-guest backup. (`:8006`/pveproxy was immune — the controller never dials it.) Diagnosis:
|
||||
`felhom.eu/documentation/tests/unattended-test-campaign-2026-06-22-8443-diagnosis.md`.
|
||||
|
||||
- `internal/web/server.go` — `Server` gains `agentCli *agentapi.Client` + `agentCliErr error` +
|
||||
`agentCliOnce sync.Once` (and the `agentapi` import).
|
||||
- `internal/web/agent_disk_handlers.go` — `agentClient()` now memoizes the build via `agentCliOnce`
|
||||
and **reuses one shared client** (cfg.LocalAPI is static per process — a config-apply self-restarts).
|
||||
The empty-endpoint "not configured" guard stays OUTSIDE the Once. All 19 call sites unchanged.
|
||||
- `internal/agentapi/client.go` — `New` Transport hardened: `MaxIdleConns:4`, `MaxIdleConnsPerHost:2`,
|
||||
`IdleConnTimeout:90s` (was a bare Transport, `IdleConnTimeout:0`). Added optional `Client.Close()`
|
||||
(CloseIdleConnections) hygiene helper.
|
||||
- Tests: `TestAgentClient_ReusesSameInstance` (+ `TestAgentClient_UnconfiguredErrors`) and
|
||||
`TestNew_TransportIdlePoolBounded` — both red-proofed against the pre-fix code.
|
||||
- Agent, its bridge-IP bind, and firewall rules were **not** touched (controller-only fix).
|
||||
Separate open item: the defense-in-depth host firewall rule scoping `:8443` to the guest bridge
|
||||
subnet is still absent (pve-firewall disabled) — to be closed independently.
|
||||
|
||||
### v0.73.0 — DR recipe: emit the secret-free customer+apps half in the hub report (2026-06-16)
|
||||
|
||||
**DR recipe slice (controller half).** Additive `dr_recipe` section on the controller's hub report — the
|
||||
|
||||
Reference in New Issue
Block a user