feat:1. 增加AnyTLS类型支持 2.定时5m全量探测节点健康改为2h,并增加配置可调整 3. 修复网页端修改配置文件保存失败问题
This commit is contained in:
@@ -136,6 +136,14 @@ type Manager struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
logger Logger
|
||||
|
||||
// periodic health check control
|
||||
healthMu sync.Mutex
|
||||
healthInterval time.Duration
|
||||
healthTimeout time.Duration
|
||||
healthTicker *time.Ticker
|
||||
healthIntervalC chan time.Duration
|
||||
probeAllInFlight atomic.Bool
|
||||
}
|
||||
|
||||
// Logger interface for logging
|
||||
@@ -199,20 +207,53 @@ func (m *Manager) StartPeriodicHealthCheck(interval, timeout time.Duration) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if interval <= 0 {
|
||||
interval = 2 * time.Hour
|
||||
}
|
||||
if timeout <= 0 {
|
||||
timeout = 10 * time.Second
|
||||
}
|
||||
|
||||
m.healthMu.Lock()
|
||||
if m.healthIntervalC == nil {
|
||||
m.healthIntervalC = make(chan time.Duration, 1)
|
||||
}
|
||||
m.healthInterval = interval
|
||||
m.healthTimeout = timeout
|
||||
if m.healthTicker != nil {
|
||||
m.healthTicker.Stop()
|
||||
}
|
||||
m.healthTicker = time.NewTicker(interval)
|
||||
ticker := m.healthTicker
|
||||
intervalC := m.healthIntervalC
|
||||
m.healthMu.Unlock()
|
||||
|
||||
go func() {
|
||||
// 启动后立即进行一次检查
|
||||
m.probeAllNodes(timeout)
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
// 启动后立即进行一次检查(走去重)
|
||||
m.RequestProbeAllOnce(timeout)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-m.ctx.Done():
|
||||
return
|
||||
case newInterval := <-intervalC:
|
||||
if newInterval <= 0 {
|
||||
newInterval = 2 * time.Hour
|
||||
}
|
||||
// 重置 ticker
|
||||
m.healthMu.Lock()
|
||||
m.healthInterval = newInterval
|
||||
if m.healthTicker != nil {
|
||||
m.healthTicker.Stop()
|
||||
}
|
||||
m.healthTicker = time.NewTicker(newInterval)
|
||||
ticker = m.healthTicker
|
||||
m.healthMu.Unlock()
|
||||
if m.logger != nil {
|
||||
m.logger.Info("periodic health check interval updated: ", newInterval)
|
||||
}
|
||||
case <-ticker.C:
|
||||
m.probeAllNodes(timeout)
|
||||
m.RequestProbeAllOnce(timeout)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -222,6 +263,41 @@ func (m *Manager) StartPeriodicHealthCheck(interval, timeout time.Duration) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetHealthCheckInterval updates the periodic health check interval at runtime.
|
||||
// It is safe to call before StartPeriodicHealthCheck; it will be applied on start.
|
||||
func (m *Manager) SetHealthCheckInterval(d time.Duration) {
|
||||
if d <= 0 {
|
||||
return
|
||||
}
|
||||
m.healthMu.Lock()
|
||||
m.healthInterval = d
|
||||
intervalC := m.healthIntervalC
|
||||
m.healthMu.Unlock()
|
||||
|
||||
if intervalC != nil {
|
||||
select {
|
||||
case intervalC <- d:
|
||||
default:
|
||||
// drop if a newer update is already queued
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RequestProbeAllOnce triggers a full probe round at most once concurrently.
|
||||
// If another full probe is already running, it returns immediately.
|
||||
func (m *Manager) RequestProbeAllOnce(timeout time.Duration) {
|
||||
if !m.probeReady {
|
||||
return
|
||||
}
|
||||
if m.probeAllInFlight.Swap(true) {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
defer m.probeAllInFlight.Store(false)
|
||||
m.probeAllNodes(timeout)
|
||||
}()
|
||||
}
|
||||
|
||||
// probeAllNodes checks all registered nodes concurrently.
|
||||
func (m *Manager) probeAllNodes(timeout time.Duration) {
|
||||
m.mu.RLock()
|
||||
|
||||
+24
-14
@@ -213,10 +213,11 @@ type allSettingsResponse struct {
|
||||
PoolBlacklistDuration string `json:"pool_blacklist_duration"`
|
||||
|
||||
// Management
|
||||
ManagementEnabled bool `json:"management_enabled"`
|
||||
ManagementListen string `json:"management_listen"`
|
||||
ManagementProbeTarget string `json:"management_probe_target"`
|
||||
ManagementPassword string `json:"management_password"`
|
||||
ManagementEnabled bool `json:"management_enabled"`
|
||||
ManagementListen string `json:"management_listen"`
|
||||
ManagementProbeTarget string `json:"management_probe_target"`
|
||||
ManagementPassword string `json:"management_password"`
|
||||
ManagementHealthCheckInterval string `json:"management_health_check_interval"`
|
||||
|
||||
// Subscription refresh
|
||||
SubRefreshEnabled bool `json:"sub_refresh_enabled"`
|
||||
@@ -264,10 +265,11 @@ type allSettingsRequest struct {
|
||||
PoolBlacklistDuration string `json:"pool_blacklist_duration"`
|
||||
|
||||
// Management
|
||||
ManagementEnabled *bool `json:"management_enabled"`
|
||||
ManagementListen string `json:"management_listen"`
|
||||
ManagementProbeTarget string `json:"management_probe_target"`
|
||||
ManagementPassword string `json:"management_password"`
|
||||
ManagementEnabled *bool `json:"management_enabled"`
|
||||
ManagementListen string `json:"management_listen"`
|
||||
ManagementProbeTarget string `json:"management_probe_target"`
|
||||
ManagementPassword string `json:"management_password"`
|
||||
ManagementHealthCheckInterval string `json:"management_health_check_interval"`
|
||||
|
||||
// Subscription refresh
|
||||
SubRefreshEnabled bool `json:"sub_refresh_enabled"`
|
||||
@@ -326,10 +328,11 @@ func (s *Server) getAllSettings() allSettingsResponse {
|
||||
PoolFailureThreshold: c.Pool.FailureThreshold,
|
||||
PoolBlacklistDuration: c.Pool.BlacklistDuration.String(),
|
||||
|
||||
ManagementEnabled: mgmtEnabled,
|
||||
ManagementListen: c.Management.Listen,
|
||||
ManagementProbeTarget: c.Management.ProbeTarget,
|
||||
ManagementPassword: c.Management.Password,
|
||||
ManagementEnabled: mgmtEnabled,
|
||||
ManagementListen: c.Management.Listen,
|
||||
ManagementProbeTarget: c.Management.ProbeTarget,
|
||||
ManagementPassword: c.Management.Password,
|
||||
ManagementHealthCheckInterval: c.Management.HealthCheckInterval.String(),
|
||||
|
||||
SubRefreshEnabled: c.SubscriptionRefresh.Enabled,
|
||||
SubRefreshInterval: c.SubscriptionRefresh.Interval.String(),
|
||||
@@ -355,7 +358,7 @@ func (s *Server) updateAllSettings(req allSettingsRequest) error {
|
||||
req.ListenerProtocol, req.MultiPortProtocol,
|
||||
req.PoolBlacklistDuration, req.SubRefreshInterval, req.SubRefreshTimeout,
|
||||
req.SubRefreshHealthCheckTimeout, req.SubRefreshDrainTimeout,
|
||||
req.GeoIPAutoUpdateInterval,
|
||||
req.GeoIPAutoUpdateInterval, req.ManagementHealthCheckInterval,
|
||||
); err != nil {
|
||||
return fmt.Errorf("参数验证失败: %w", err)
|
||||
}
|
||||
@@ -410,6 +413,9 @@ func (s *Server) updateAllSettings(req allSettingsRequest) error {
|
||||
c.Management.Listen = req.ManagementListen
|
||||
c.Management.ProbeTarget = strings.TrimSpace(req.ManagementProbeTarget)
|
||||
c.Management.Password = req.ManagementPassword
|
||||
if d, err := time.ParseDuration(req.ManagementHealthCheckInterval); err == nil && d > 0 {
|
||||
c.Management.HealthCheckInterval = d
|
||||
}
|
||||
|
||||
// Subscription refresh
|
||||
c.SubscriptionRefresh.Enabled = req.SubRefreshEnabled
|
||||
@@ -456,6 +462,10 @@ func (s *Server) updateAllSettings(req allSettingsRequest) error {
|
||||
s.logger.Printf("更新探测目标失败: %v", err)
|
||||
}
|
||||
}
|
||||
// 动态更新周期健康检查间隔,使其立即生效
|
||||
if c.Management.HealthCheckInterval > 0 && s.mgr != nil {
|
||||
s.mgr.SetHealthCheckInterval(c.Management.HealthCheckInterval)
|
||||
}
|
||||
|
||||
s.logger.Printf("✅ 设置已保存并同步到运行时")
|
||||
return nil
|
||||
@@ -1086,7 +1096,7 @@ func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
writeJSON(w, map[string]any{
|
||||
"message": "设置已保存",
|
||||
"need_reload": true,
|
||||
"need_reload": false,
|
||||
})
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
|
||||
Reference in New Issue
Block a user