perf(runtime): pool the local xray gRPC connection (#6916)

A client per runtime operation made a bulk delete pay a TCP and HTTP/2
handshake for every client. The local runtime now keeps one gRPC client
per API port and replaces it when a core restart moves the port; the
traffic poll keeps its own client, since StatsLastValues is unlocked.

A pooled channel keeps grpc-go's reconnect backoff after the core
restarts, so a call that fails with Unavailable drops the client and
dials again, as a client per call did.
This commit is contained in:
25f2002654
2026-10-24 23:00:09 +05:30
committed by GitHub
parent de21c128fc
commit f105132866
3 changed files with 186 additions and 4 deletions
+52
View File
@@ -0,0 +1,52 @@
package runtime
import (
"sync"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
)
// One pooled gRPC client serves local runtime operations; never use it for traffic stats, whose
// unlocked StatsLastValues a second reader would corrupt, so the traffic poll keeps its own client.
var (
localAPIMu sync.Mutex
localAPIPort int
localAPIClient *xray.XrayAPI
)
// localAPIForPort returns the pooled client for the API port, replacing (and closing) it when a core
// restart moved the port; callers must not close it.
func localAPIForPort(port int) (*xray.XrayAPI, error) {
localAPIMu.Lock()
defer localAPIMu.Unlock()
if localAPIClient != nil {
if localAPIPort == port {
return localAPIClient, nil
}
localAPIClient.Close()
localAPIClient = nil
}
api := &xray.XrayAPI{}
if err := api.Init(port); err != nil {
return nil, err
}
localAPIClient = api
localAPIPort = port
return api, nil
}
// evictLocalAPIClient closes the pooled client so the next call dials a fresh connection, unless
// another call already replaced the one that failed.
func evictLocalAPIClient(api *xray.XrayAPI) {
localAPIMu.Lock()
defer localAPIMu.Unlock()
if localAPIClient != api {
return
}
localAPIClient.Close()
localAPIClient = nil
localAPIPort = 0
}