diff --git a/constant/timeout.go b/constant/timeout.go index db0379a4..756028ca 100644 --- a/constant/timeout.go +++ b/constant/timeout.go @@ -3,11 +3,12 @@ package constant import "time" const ( - TCPTimeout = 5 * time.Second - ReadPayloadTimeout = 300 * time.Millisecond - DNSTimeout = 10 * time.Second - QUICTimeout = 30 * time.Second - STUNTimeout = 15 * time.Second - UDPTimeout = 5 * time.Minute - DefaultURLTestInterval = 1 * time.Minute + TCPTimeout = 5 * time.Second + ReadPayloadTimeout = 300 * time.Millisecond + DNSTimeout = 10 * time.Second + QUICTimeout = 30 * time.Second + STUNTimeout = 15 * time.Second + UDPTimeout = 5 * time.Minute + DefaultURLTestInterval = 3 * time.Minute + DefaultURLTestIdleTimeout = 30 * time.Minute ) diff --git a/docs/configuration/outbound/urltest.md b/docs/configuration/outbound/urltest.md index d905068d..f4b3b0aa 100644 --- a/docs/configuration/outbound/urltest.md +++ b/docs/configuration/outbound/urltest.md @@ -10,9 +10,10 @@ "proxy-b", "proxy-c" ], - "url": "https://www.gstatic.com/generate_204", - "interval": "1m", - "tolerance": 50, + "url": "", + "interval": "", + "tolerance": 0, + "idle_timeout": "", "interrupt_exist_connections": false } ``` @@ -31,12 +32,16 @@ The URL to test. `https://www.gstatic.com/generate_204` will be used if empty. #### interval -The test interval. `1m` will be used if empty. +The test interval. `3m` will be used if empty. #### tolerance The test tolerance in milliseconds. `50` will be used if empty. +#### idle_timeout + +The idle timeout. `30m` will be used if empty. + #### interrupt_exist_connections Interrupt existing connections when the selected outbound has changed. diff --git a/docs/configuration/outbound/urltest.zh.md b/docs/configuration/outbound/urltest.zh.md index 0ad891f6..4372298a 100644 --- a/docs/configuration/outbound/urltest.zh.md +++ b/docs/configuration/outbound/urltest.zh.md @@ -10,9 +10,10 @@ "proxy-b", "proxy-c" ], - "url": "https://www.gstatic.com/generate_204", - "interval": "1m", + "url": "", + "interval": "", "tolerance": 50, + "idle_timeout": "", "interrupt_exist_connections": false } ``` @@ -31,12 +32,16 @@ #### interval -测试间隔。 默认使用 `1m`。 +测试间隔。 默认使用 `3m`。 #### tolerance 以毫秒为单位的测试容差。 默认使用 `50`。 +#### idle_timeout + +空闲超时。默认使用 `30m`。 + #### interrupt_exist_connections 当选定的出站发生更改时,中断现有连接。 diff --git a/option/group.go b/option/group.go index 58824e80..72a0f637 100644 --- a/option/group.go +++ b/option/group.go @@ -11,5 +11,6 @@ type URLTestOutboundOptions struct { URL string `json:"url,omitempty"` Interval Duration `json:"interval,omitempty"` Tolerance uint16 `json:"tolerance,omitempty"` + IdleTimeout Duration `json:"idle_timeout,omitempty"` InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` } diff --git a/outbound/urltest.go b/outbound/urltest.go index 3b14d95b..5cae5236 100644 --- a/outbound/urltest.go +++ b/outbound/urltest.go @@ -35,6 +35,7 @@ type URLTest struct { link string interval time.Duration tolerance uint16 + idleTimeout time.Duration group *URLTestGroup interruptExternalConnections bool } @@ -53,6 +54,7 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo link: options.URL, interval: time.Duration(options.Interval), tolerance: options.Tolerance, + idleTimeout: time.Duration(options.IdleTimeout), interruptExternalConnections: options.InterruptExistConnections, } if len(outbound.tags) == 0 { @@ -77,7 +79,21 @@ func (s *URLTest) Start() error { } outbounds = append(outbounds, detour) } - s.group = NewURLTestGroup(s.ctx, s.router, s.logger, outbounds, s.link, s.interval, s.tolerance, s.interruptExternalConnections) + group, err := NewURLTestGroup( + s.ctx, + s.router, + s.logger, + outbounds, + s.link, + s.interval, + s.tolerance, + s.idleTimeout, + s.interruptExternalConnections, + ) + if err != nil { + return err + } + s.group = group return nil } @@ -155,6 +171,7 @@ type URLTestGroup struct { link string interval time.Duration tolerance uint16 + idleTimeout time.Duration history *urltest.HistoryStorage checking atomic.Bool pauseManager pause.Manager @@ -178,14 +195,21 @@ func NewURLTestGroup( link string, interval time.Duration, tolerance uint16, + idleTimeout time.Duration, interruptExternalConnections bool, -) *URLTestGroup { +) (*URLTestGroup, error) { if interval == 0 { interval = C.DefaultURLTestInterval } if tolerance == 0 { tolerance = 50 } + if idleTimeout == 0 { + idleTimeout = C.DefaultURLTestIdleTimeout + } + if interval > idleTimeout { + return nil, E.New("interval must be less or equal than idle_timeout") + } var history *urltest.HistoryStorage if history = service.PtrFromContext[urltest.HistoryStorage](ctx); history != nil { } else if clashServer := router.ClashServer(); clashServer != nil { @@ -201,12 +225,13 @@ func NewURLTestGroup( link: link, interval: interval, tolerance: tolerance, + idleTimeout: idleTimeout, history: history, close: make(chan struct{}), pauseManager: pause.ManagerFromContext(ctx), interruptGroup: interrupt.NewGroup(), interruptExternalConnections: interruptExternalConnections, - } + }, nil } func (g *URLTestGroup) PostStart() { @@ -273,6 +298,7 @@ func (g *URLTestGroup) Select(network string) adapter.Outbound { func (g *URLTestGroup) loopCheck() { if time.Now().Sub(g.lastActive.Load()) > g.interval { + g.lastActive.Store(time.Now()) g.CheckOutbounds(false) } for { @@ -281,6 +307,13 @@ func (g *URLTestGroup) loopCheck() { return case <-g.ticker.C: } + if time.Now().Sub(g.lastActive.Load()) > g.idleTimeout { + g.access.Lock() + g.ticker.Stop() + g.ticker = nil + g.access.Unlock() + return + } g.pauseManager.WaitActive() g.CheckOutbounds(false) }