2a5b88fbf9
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>
31 lines
1.0 KiB
Go
31 lines
1.0 KiB
Go
package agentapi
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// T2: New must build a Transport with a BOUNDED, EXPIRING idle-conn pool. The earlier bare
|
|
// &http.Transport{TLSClientConfig:…} had IdleConnTimeout==0 (idle keep-alives never expire) — the
|
|
// leak. White-box (package agentapi) so we can read the unexported hc.
|
|
//
|
|
// Companion red-proof: the bare Transport gives IdleConnTimeout==0 → this test fails (demonstrated,
|
|
// then reverted, during implementation).
|
|
func TestNew_TransportIdlePoolBounded(t *testing.T) {
|
|
c, err := New("127.0.0.1:8443", "tok", strings.Repeat("a", 64))
|
|
if err != nil {
|
|
t.Fatalf("New: %v", err)
|
|
}
|
|
tr, ok := c.hc.Transport.(*http.Transport)
|
|
if !ok {
|
|
t.Fatalf("unexpected transport type %T", c.hc.Transport)
|
|
}
|
|
if tr.IdleConnTimeout <= 0 {
|
|
t.Fatalf("IdleConnTimeout must be > 0 (idle keep-alives must expire); got %v", tr.IdleConnTimeout)
|
|
}
|
|
if tr.MaxIdleConnsPerHost <= 0 {
|
|
t.Fatalf("MaxIdleConnsPerHost must be > 0 (bounded pool); got %d", tr.MaxIdleConnsPerHost)
|
|
}
|
|
}
|