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:
@@ -227,7 +227,7 @@ type loadConnection struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *loadConnection) run(ctx context.Context, onError func(error)) {
|
func (c *loadConnection) run(ctx context.Context, onError func(error)) {
|
||||||
defer c.client.CloseIdleConnections()
|
defer closeMeasurementClient(c.client)
|
||||||
markActive := func() {
|
markActive := func() {
|
||||||
c.ready.Store(true)
|
c.ready.Store(true)
|
||||||
c.active.Store(true)
|
c.active.Store(true)
|
||||||
@@ -451,29 +451,31 @@ func (r *directionRunner) startProber(ctx context.Context) {
|
|||||||
if conn == nil {
|
if conn == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
go func(selfClient *http.Client) {
|
r.runProbeRound(ctx, conn.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)
|
|
||||||
ticker.Reset(r.probeInterval())
|
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 {
|
func (r *directionRunner) probeInterval() time.Duration {
|
||||||
interval := time.Second / time.Duration(settings.maxProbesPerSecond)
|
interval := time.Second / time.Duration(settings.maxProbesPerSecond)
|
||||||
capacity := r.currentCapacity.Load()
|
capacity := r.currentCapacity.Load()
|
||||||
@@ -945,7 +947,7 @@ func measureIdleLatency(ctx context.Context, factory MeasurementClientFactory, c
|
|||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
measurement, err := runProbe(ctx, client, config.smallURL.String(), false)
|
measurement, err := runProbe(ctx, client, config.smallURL.String(), false)
|
||||||
client.CloseIdleConnections()
|
closeMeasurementClient(client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
@@ -1274,6 +1276,16 @@ func newRequest(ctx context.Context, method string, rawURL string, body io.Reade
|
|||||||
return req, nil
|
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 {
|
func validateResponse(resp *http.Response) error {
|
||||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
return E.New("unexpected status: ", resp.Status)
|
return E.New("unexpected status: ", resp.Status)
|
||||||
|
|||||||
Reference in New Issue
Block a user