mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-05-13 13:57:05 +00:00
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:
parent
acfb90a38e
commit
5cd421401d
1 changed files with 32 additions and 20 deletions
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue