Fix goroutine leak in networkquality tool

Serialize probe rounds in startProber to eliminate unbounded fan-out of
fire-and-forget probe goroutines (up to 100/sec per direction), and close
HTTP/3 transports via transport.Close() in addition to CloseIdleConnections.
This commit is contained in:
世界 2026-04-21 22:30:58 +08:00
parent acfb90a38e
commit 5cd421401d
No known key found for this signature in database
GPG key ID: CD109927C34A63C4

View file

@ -227,7 +227,7 @@ type loadConnection struct {
}
func (c *loadConnection) run(ctx context.Context, onError func(error)) {
defer c.client.CloseIdleConnections()
defer closeMeasurementClient(c.client)
markActive := func() {
c.ready.Store(true)
c.active.Store(true)
@ -451,29 +451,31 @@ func (r *directionRunner) startProber(ctx context.Context) {
if conn == nil {
continue
}
go func(selfClient *http.Client) {
foreignClient, err := r.factory(r.plan.connectEndpoint, true, true, nil, nil)
if err != nil {
return
}
round, err := collectProbeRound(ctx, foreignClient, selfClient, r.plan.probeURL.String())
foreignClient.CloseIdleConnections()
if err != nil {
return
}
r.recordProbeRound(probeRound{
interval: int(r.currentInterval.Load()),
tcp: round.tcp,
tls: round.tls,
httpFirst: round.httpFirst,
httpLoaded: round.httpLoaded,
})
}(conn.client)
r.runProbeRound(ctx, conn.client)
ticker.Reset(r.probeInterval())
}
}()
}
func (r *directionRunner) runProbeRound(ctx context.Context, selfClient *http.Client) {
foreignClient, err := r.factory(r.plan.connectEndpoint, true, true, nil, nil)
if err != nil {
return
}
defer closeMeasurementClient(foreignClient)
round, err := collectProbeRound(ctx, foreignClient, selfClient, r.plan.probeURL.String())
if err != nil {
return
}
r.recordProbeRound(probeRound{
interval: int(r.currentInterval.Load()),
tcp: round.tcp,
tls: round.tls,
httpFirst: round.httpFirst,
httpLoaded: round.httpLoaded,
})
}
func (r *directionRunner) probeInterval() time.Duration {
interval := time.Second / time.Duration(settings.maxProbesPerSecond)
capacity := r.currentCapacity.Load()
@ -945,7 +947,7 @@ func measureIdleLatency(ctx context.Context, factory MeasurementClientFactory, c
return 0, 0, err
}
measurement, err := runProbe(ctx, client, config.smallURL.String(), false)
client.CloseIdleConnections()
closeMeasurementClient(client)
if err != nil {
return 0, 0, err
}
@ -1274,6 +1276,16 @@ func newRequest(ctx context.Context, method string, rawURL string, body io.Reade
return req, nil
}
func closeMeasurementClient(client *http.Client) {
if client == nil {
return
}
client.CloseIdleConnections()
if closer, ok := client.Transport.(interface{ Close() error }); ok {
_ = closer.Close()
}
}
func validateResponse(resp *http.Response) error {
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return E.New("unexpected status: ", resp.Status)