Fix URLTest group hang on unresponsive outbound

This commit is contained in:
世界
2026-08-21 18:21:51 +08:00
parent 9d8d4b8796
commit e4a4e8c790
6 changed files with 84 additions and 15 deletions
+28 -6
View File
@@ -115,6 +115,10 @@ func (s *URLTest) CheckOutbounds() {
s.group.CheckOutbounds(true)
}
func (s *URLTest) PerformUpdateCheck() {
s.group.performUpdateCheck()
}
func (s *URLTest) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
s.group.Touch()
var outbound adapter.Outbound
@@ -202,6 +206,7 @@ type URLTestGroup struct {
interruptGroup *interrupt.Group
interruptExternalConnections bool
access sync.Mutex
updateAccess sync.Mutex
ticker *time.Ticker
close chan struct{}
started bool
@@ -362,6 +367,11 @@ func (g *URLTestGroup) URLTest(ctx context.Context) (map[string]uint16, error) {
return g.urlTest(ctx, false)
}
type urlTestResult struct {
delay uint16
err error
}
func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint16, error) {
result := make(map[string]uint16)
if g.checking.Swap(true) {
@@ -389,18 +399,28 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint
b.Go(realTag, func() (any, error) {
testCtx, cancel := context.WithTimeout(g.ctx, C.TCPTimeout)
defer cancel()
t, err := urltest.URLTest(testCtx, g.link, p)
if err != nil {
g.logger.Debug("outbound ", tag, " unavailable: ", err)
testChan := make(chan urlTestResult, 1)
go func() {
delay, testErr := urltest.URLTest(testCtx, g.link, p)
testChan <- urlTestResult{delay, testErr}
}()
var testResult urlTestResult
select {
case testResult = <-testChan:
case <-testCtx.Done():
testResult.err = testCtx.Err()
}
if testResult.err != nil {
g.logger.Debug("outbound ", tag, " unavailable: ", testResult.err)
g.history.DeleteURLTestHistory(realTag)
} else {
g.logger.Debug("outbound ", tag, " available: ", t, "ms")
g.logger.Debug("outbound ", tag, " available: ", testResult.delay, "ms")
g.history.StoreURLTestHistory(realTag, &adapter.URLTestHistory{
Time: time.Now(),
Delay: t,
Delay: testResult.delay,
})
resultAccess.Lock()
result[tag] = t
result[tag] = testResult.delay
resultAccess.Unlock()
}
return nil, nil
@@ -412,6 +432,8 @@ func (g *URLTestGroup) urlTest(ctx context.Context, force bool) (map[string]uint
}
func (g *URLTestGroup) performUpdateCheck() {
g.updateAccess.Lock()
defer g.updateAccess.Unlock()
var updated bool
if outbound, exists := g.Select(N.NetworkTCP); outbound != nil && (g.selectedOutboundTCP == nil || (exists && outbound != g.selectedOutboundTCP)) {
if g.selectedOutboundTCP != nil {
+18 -6
View File
@@ -35,7 +35,6 @@ var _ adapter.InterfaceUpdateListener = (*Outbound)(nil)
type Outbound struct {
outbound.Adapter
ctx context.Context
logger logger.ContextLogger
dialer N.Dialer
serverAddr M.Socksaddr
@@ -56,7 +55,6 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
}
outbound := &Outbound{
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSSH, tag, []string{N.NetworkTCP}, options.DialerOptions),
ctx: ctx,
logger: logger,
dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(),
@@ -121,7 +119,7 @@ func randomVersion() string {
return version
}
func (s *Outbound) connect() (*ssh.Client, error) {
func (s *Outbound) connect(ctx context.Context) (client *ssh.Client, err error) {
if s.client != nil {
return s.client, nil
}
@@ -133,10 +131,24 @@ func (s *Outbound) connect() (*ssh.Client, error) {
return s.client, nil
}
conn, err := s.dialer.DialContext(s.ctx, N.NetworkTCP, s.serverAddr)
conn, err := s.dialer.DialContext(ctx, N.NetworkTCP, s.serverAddr)
if err != nil {
return nil, err
}
if ctx.Done() != nil {
handshakeConn := conn
stopContext := context.AfterFunc(ctx, func() {
_ = handshakeConn.Close()
})
defer func() {
if !stopContext() {
s.client = nil
s.clientConn = nil
client = nil
err = ctx.Err()
}
}()
}
config := &ssh.ClientConfig{
User: s.user,
Auth: s.authMethod,
@@ -161,7 +173,7 @@ func (s *Outbound) connect() (*ssh.Client, error) {
return nil, E.Cause(err, "connect to ssh server")
}
client := ssh.NewClient(clientConn, chans, reqs)
client = ssh.NewClient(clientConn, chans, reqs)
s.clientConn = conn
s.client = client
@@ -187,7 +199,7 @@ func (s *Outbound) Close() error {
}
func (s *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
client, err := s.connect()
client, err := s.connect(ctx)
if err != nil {
return nil, err
}