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:
@@ -38,13 +38,25 @@ func (s *Server) ServeDiskAPI(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// agentClient builds a pinned client for the host agent's per-guest local API.
|
||||
// agentClient returns the shared pinned client for the host agent's per-guest local API.
|
||||
// Returns a clear error if the local API is not configured (unprovisioned guest).
|
||||
//
|
||||
// The client is built ONCE and reused (cfg.LocalAPI is static per process — a config-apply triggers a
|
||||
// graceful self-restart). Reusing one *agentapi.Client (one pooled http.Transport) eliminates the
|
||||
// per-call Transport leak that accumulated idle ESTABLISHED sockets to the agent's :8443 and exhausted
|
||||
// the ephemeral source-port range after ~5 days of uptime. *agentapi.Client/*http.Client are safe for
|
||||
// concurrent use, so the (19) callers need no extra locking. The empty-endpoint guard stays OUTSIDE the
|
||||
// Once so it is re-checked each call (defensive; the endpoint is static); only a real build result
|
||||
// (client, or a construction error like a bad fingerprint — neither changes without a restart) is memoized.
|
||||
func (s *Server) agentClient() (*agentapi.Client, error) {
|
||||
if s.cfg.LocalAPI.Endpoint == "" {
|
||||
return nil, errors.New("agent not configured")
|
||||
}
|
||||
return agentapi.New(s.cfg.LocalAPI.Endpoint, s.cfg.LocalAPI.Token, s.cfg.LocalAPI.Fingerprint)
|
||||
s.agentCliOnce.Do(func() {
|
||||
s.agentCli, s.agentCliErr = agentapi.New(
|
||||
s.cfg.LocalAPI.Endpoint, s.cfg.LocalAPI.Token, s.cfg.LocalAPI.Fingerprint)
|
||||
})
|
||||
return s.agentCli, s.agentCliErr
|
||||
}
|
||||
|
||||
// writeDiskJSON writes the standard {ok,data,error} envelope used by the disk API.
|
||||
|
||||
Reference in New Issue
Block a user