Browse Source

Add `idle_timeout` for URLTest outbound

世界 1 year ago
parent
commit
f1e3a59db3

+ 8 - 7
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
 )

+ 9 - 4
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.

+ 8 - 3
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
 
 当选定的出站发生更改时,中断现有连接。

+ 1 - 0
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"`
 }

+ 36 - 3
outbound/urltest.go

@@ -35,6 +35,7 @@ type URLTest struct {
 	link                         string
 	interval                     time.Duration
 	tolerance                    uint16
+	idleTimeout                  time.Duration
 	group                        *URLTestGroup
 	interruptExternalConnections bool
 }
@@ -54,6 +55,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 {
@@ -71,7 +73,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
 }
 
@@ -160,6 +176,7 @@ type URLTestGroup struct {
 	link                         string
 	interval                     time.Duration
 	tolerance                    uint16
+	idleTimeout                  time.Duration
 	history                      *urltest.HistoryStorage
 	checking                     atomic.Bool
 	pauseManager                 pause.Manager
@@ -183,14 +200,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 {
@@ -206,12 +230,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() {
@@ -278,6 +303,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 {
@@ -286,6 +312,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)
 	}