Add idle_timeout for URLTest outbound

This commit is contained in:
世界 2023-12-03 11:57:53 +08:00
parent d1ad342cb0
commit fc9804b20f
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
5 changed files with 62 additions and 17 deletions

View file

@ -3,11 +3,12 @@ package constant
import "time" import "time"
const ( const (
TCPTimeout = 5 * time.Second TCPTimeout = 5 * time.Second
ReadPayloadTimeout = 300 * time.Millisecond ReadPayloadTimeout = 300 * time.Millisecond
DNSTimeout = 10 * time.Second DNSTimeout = 10 * time.Second
QUICTimeout = 30 * time.Second QUICTimeout = 30 * time.Second
STUNTimeout = 15 * time.Second STUNTimeout = 15 * time.Second
UDPTimeout = 5 * time.Minute UDPTimeout = 5 * time.Minute
DefaultURLTestInterval = 1 * time.Minute DefaultURLTestInterval = 3 * time.Minute
DefaultURLTestIdleTimeout = 30 * time.Minute
) )

View file

@ -10,9 +10,10 @@
"proxy-b", "proxy-b",
"proxy-c" "proxy-c"
], ],
"url": "https://www.gstatic.com/generate_204", "url": "",
"interval": "1m", "interval": "",
"tolerance": 50, "tolerance": 0,
"idle_timeout": "",
"interrupt_exist_connections": false "interrupt_exist_connections": false
} }
``` ```
@ -31,12 +32,16 @@ The URL to test. `https://www.gstatic.com/generate_204` will be used if empty.
#### interval #### interval
The test interval. `1m` will be used if empty. The test interval. `3m` will be used if empty.
#### tolerance #### tolerance
The test tolerance in milliseconds. `50` will be used if empty. 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_exist_connections
Interrupt existing connections when the selected outbound has changed. Interrupt existing connections when the selected outbound has changed.

View file

@ -10,9 +10,10 @@
"proxy-b", "proxy-b",
"proxy-c" "proxy-c"
], ],
"url": "https://www.gstatic.com/generate_204", "url": "",
"interval": "1m", "interval": "",
"tolerance": 50, "tolerance": 50,
"idle_timeout": "",
"interrupt_exist_connections": false "interrupt_exist_connections": false
} }
``` ```
@ -31,12 +32,16 @@
#### interval #### interval
测试间隔。 默认使用 `1m`。 测试间隔。 默认使用 `3m`。
#### tolerance #### tolerance
以毫秒为单位的测试容差。 默认使用 `50` 以毫秒为单位的测试容差。 默认使用 `50`
#### idle_timeout
空闲超时。默认使用 `30m`
#### interrupt_exist_connections #### interrupt_exist_connections
当选定的出站发生更改时,中断现有连接。 当选定的出站发生更改时,中断现有连接。

View file

@ -11,5 +11,6 @@ type URLTestOutboundOptions struct {
URL string `json:"url,omitempty"` URL string `json:"url,omitempty"`
Interval Duration `json:"interval,omitempty"` Interval Duration `json:"interval,omitempty"`
Tolerance uint16 `json:"tolerance,omitempty"` Tolerance uint16 `json:"tolerance,omitempty"`
IdleTimeout Duration `json:"idle_timeout,omitempty"`
InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"`
} }

View file

@ -35,6 +35,7 @@ type URLTest struct {
link string link string
interval time.Duration interval time.Duration
tolerance uint16 tolerance uint16
idleTimeout time.Duration
group *URLTestGroup group *URLTestGroup
interruptExternalConnections bool interruptExternalConnections bool
} }
@ -53,6 +54,7 @@ func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLo
link: options.URL, link: options.URL,
interval: time.Duration(options.Interval), interval: time.Duration(options.Interval),
tolerance: options.Tolerance, tolerance: options.Tolerance,
idleTimeout: time.Duration(options.IdleTimeout),
interruptExternalConnections: options.InterruptExistConnections, interruptExternalConnections: options.InterruptExistConnections,
} }
if len(outbound.tags) == 0 { if len(outbound.tags) == 0 {
@ -77,7 +79,21 @@ func (s *URLTest) Start() error {
} }
outbounds = append(outbounds, detour) 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 return nil
} }
@ -155,6 +171,7 @@ type URLTestGroup struct {
link string link string
interval time.Duration interval time.Duration
tolerance uint16 tolerance uint16
idleTimeout time.Duration
history *urltest.HistoryStorage history *urltest.HistoryStorage
checking atomic.Bool checking atomic.Bool
pauseManager pause.Manager pauseManager pause.Manager
@ -178,14 +195,21 @@ func NewURLTestGroup(
link string, link string,
interval time.Duration, interval time.Duration,
tolerance uint16, tolerance uint16,
idleTimeout time.Duration,
interruptExternalConnections bool, interruptExternalConnections bool,
) *URLTestGroup { ) (*URLTestGroup, error) {
if interval == 0 { if interval == 0 {
interval = C.DefaultURLTestInterval interval = C.DefaultURLTestInterval
} }
if tolerance == 0 { if tolerance == 0 {
tolerance = 50 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 var history *urltest.HistoryStorage
if history = service.PtrFromContext[urltest.HistoryStorage](ctx); history != nil { if history = service.PtrFromContext[urltest.HistoryStorage](ctx); history != nil {
} else if clashServer := router.ClashServer(); clashServer != nil { } else if clashServer := router.ClashServer(); clashServer != nil {
@ -201,12 +225,13 @@ func NewURLTestGroup(
link: link, link: link,
interval: interval, interval: interval,
tolerance: tolerance, tolerance: tolerance,
idleTimeout: idleTimeout,
history: history, history: history,
close: make(chan struct{}), close: make(chan struct{}),
pauseManager: pause.ManagerFromContext(ctx), pauseManager: pause.ManagerFromContext(ctx),
interruptGroup: interrupt.NewGroup(), interruptGroup: interrupt.NewGroup(),
interruptExternalConnections: interruptExternalConnections, interruptExternalConnections: interruptExternalConnections,
} }, nil
} }
func (g *URLTestGroup) PostStart() { func (g *URLTestGroup) PostStart() {
@ -273,6 +298,7 @@ func (g *URLTestGroup) Select(network string) adapter.Outbound {
func (g *URLTestGroup) loopCheck() { func (g *URLTestGroup) loopCheck() {
if time.Now().Sub(g.lastActive.Load()) > g.interval { if time.Now().Sub(g.lastActive.Load()) > g.interval {
g.lastActive.Store(time.Now())
g.CheckOutbounds(false) g.CheckOutbounds(false)
} }
for { for {
@ -281,6 +307,13 @@ func (g *URLTestGroup) loopCheck() {
return return
case <-g.ticker.C: 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.pauseManager.WaitActive()
g.CheckOutbounds(false) g.CheckOutbounds(false)
} }