fix(node): drop online clients and sub-nodes of nodes no longer synced

What the master derives from a node's reports (online clients, active
inbounds, learned sub-nodes) must live only while that node is still
synced; ClearNodeOnlineClients states it: a downed node must not keep its
clients listed as online.

Only a failed snapshot fetch cleared the online set, and only a failed
probe cleared sub-nodes. A disabled node (both jobs skip it), a node marked
offline before the sync tick reached it, a deleted node, and a node whose
snapshot fetched but failed to merge all kept their clients online in
onlineClients, onlineByGuid and activeInbounds, which the dashboard and a
parent master's /clients/onlines read. Disabled and deleted nodes also kept
their sub-nodes on the Nodes page until the panel restarted.

The traffic sync now keeps online sets only for enabled, online nodes in
its list, the heartbeat keeps sub-nodes only for enabled listed nodes, both
before the empty-list return, and a failed merge clears like a failed
fetch. The sync job's one-line call has no job-level test: that package
cannot install the xray process, so RetainSyncedNodeOnlineClients carries
the tested rule.
This commit is contained in:
Sanaei
2026-09-15 19:27:38 +02:00
parent ea66aa4971
commit a84bbeab2e
7 changed files with 220 additions and 0 deletions
+17
View File
@@ -538,6 +538,23 @@ func (p *Process) ClearNodeOnlineClients(nodeID int) {
delete(p.nodeActiveInboundTrees, nodeID)
}
// RetainNodeOnlineClients drops the subtree of every direct node keep rejects: nodes
// the master stopped syncing without a failed probe (disabled, offline, deleted).
func (p *Process) RetainNodeOnlineClients(keep func(nodeID int) bool) {
p.onlineMu.Lock()
defer p.onlineMu.Unlock()
for nodeID := range p.nodeOnlineTrees {
if !keep(nodeID) {
delete(p.nodeOnlineTrees, nodeID)
}
}
for nodeID := range p.nodeActiveInboundTrees {
if !keep(nodeID) {
delete(p.nodeActiveInboundTrees, nodeID)
}
}
}
// GetUptime returns the uptime of the Xray process in seconds.
func (p *Process) GetUptime() uint64 {
return uint64(time.Since(p.startTime).Seconds())