Fix group status updates broken by API service

The URL test history update hook and the Clash mode update hook were
single-slot: the API service's attached service overwrote the hook set
by the daemon, so clients stopped receiving group updates. Replace both
with multicast hook lists.

Also share a single URL test history storage via context: Clash API
looked it up under a key nobody registered and fell back to its own
empty storage, so dashboards showed no delay once an API service was
configured. Selector changes now notify through the shared storage,
covering selections made from any API surface.
This commit is contained in:
世界
2026-06-13 09:30:11 +08:00
parent 266f590b92
commit 22650b3020
9 changed files with 49 additions and 52 deletions
+6
View File
@@ -8,6 +8,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/common/interrupt"
"github.com/sagernet/sing-box/common/urltest"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
@@ -40,6 +41,7 @@ type Selector struct {
defaultTag string
outbounds map[string]adapter.Outbound
selected common.TypedValue[adapter.Outbound]
history *urltest.HistoryStorage
interruptGroup *interrupt.Group
interruptExternalConnections bool
}
@@ -54,6 +56,7 @@ func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextL
tags: options.Outbounds,
defaultTag: options.Default,
outbounds: make(map[string]adapter.Outbound),
history: service.PtrFromContext[urltest.HistoryStorage](ctx),
interruptGroup: interrupt.NewGroup(),
interruptExternalConnections: options.InterruptExistConnections,
}
@@ -137,6 +140,9 @@ func (s *Selector) SelectOutbound(tag string) bool {
}
}
s.interruptGroup.Interrupt(s.interruptExternalConnections)
if s.history != nil {
s.history.NotifyUpdated()
}
return true
}
+4 -8
View File
@@ -195,7 +195,7 @@ type URLTestGroup struct {
interval time.Duration
tolerance uint16
idleTimeout time.Duration
history adapter.URLTestHistoryStorage
history *urltest.HistoryStorage
checking atomic.Bool
selectedOutboundTCP adapter.Outbound
selectedOutboundUDP adapter.Outbound
@@ -221,13 +221,9 @@ func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManage
if interval > idleTimeout {
return nil, E.New("interval must be less or equal than idle_timeout")
}
var history adapter.URLTestHistoryStorage
if historyFromCtx := service.PtrFromContext[urltest.HistoryStorage](ctx); historyFromCtx != nil {
history = historyFromCtx
} else if clashServer := service.FromContext[adapter.ClashServer](ctx); clashServer != nil {
history = clashServer.HistoryStorage()
} else {
history = urltest.NewHistoryStorage()
history := service.PtrFromContext[urltest.HistoryStorage](ctx)
if history == nil {
return nil, E.New("missing URL test history storage")
}
return &URLTestGroup{
ctx: ctx,