fix(overlay): reuse shared http.Transport to stop idle conn goroutine leak

송인서 committed May 1, 2026 at 12:06 UTC e2a1c7f64b55bc867fb8257c5a7025bbb838ad14
1 file changed +27 -14
portal/overlay/overlay.go
+27 -14
@@ -66,10 +66,12 @@ func NormalizeConfig(cfg Config) (Config, error) {
66 }
67
68 type Overlay struct {
69 - cfg Config
70 - stack *stack
71 - listener net.Listener
72 - server *http.Server
69 + cfg Config
70 + stack *stack
71 + listener net.Listener
72 + server *http.Server
73 + transport *http.Transport
74 + client *http.Client
75 }
76
77 func NewOverlay(cfg Config, handler http.Handler) (*Overlay, error) {
@@ -98,13 +100,26 @@ func NewOverlay(cfg Config, handler http.Handler) (*Overlay, error) {
100 ReadHeaderTimeout: 10 * time.Second,
101 }
102
103 + transport := &http.Transport{
104 + DialContext: stack.DialContext,
105 + TLSHandshakeTimeout: 10 * time.Second,
106 + MaxIdleConns: 100,
107 + IdleConnTimeout: 90 * time.Second,
108 + ResponseHeaderTimeout: 30 * time.Second,
109 + ExpectContinueTimeout: 1 * time.Second,
110 + ForceAttemptHTTP2: false,
111 + }
112 + client := &http.Client{Transport: transport}
113 +
114 publicCfg := cfg.Copy()
115 publicCfg.PrivateKey = ""
116 return &Overlay{
104 - cfg: publicCfg,
105 - stack: stack,
106 - listener: listener,
107 - server: server,
117 + cfg: publicCfg,
118 + stack: stack,
119 + listener: listener,
120 + server: server,
121 + transport: transport,
122 + client: client,
123 }, nil
124 }
125
@@ -139,6 +154,9 @@ func (o *Overlay) Shutdown(ctx context.Context) error {
154 shutdownErr = errors.Join(shutdownErr, err)
155 }
156 }
157 + if o.transport != nil {
158 + o.transport.CloseIdleConnections()
159 + }
160 if o.listener != nil {
161 err := o.listener.Close()
162 if err != nil && !errors.Is(err, net.ErrClosed) {
@@ -155,12 +173,7 @@ func (o *Overlay) Client() *http.Client {
173 if o == nil || o.stack == nil {
174 return nil
175 }
158 - return &http.Client{
159 - Transport: &http.Transport{
160 - DialContext: o.stack.DialContext,
161 - ForceAttemptHTTP2: false,
162 - },
163 - }
176 + return o.client
177 }
178
179 func (o *Overlay) DiscoverRelay(ctx context.Context, relay types.RelayDescriptor) (types.DiscoveryResponse, error) {