fix: start/stop for ring-only forwards + group sync via ring topology

Three issues fixed:

1. Start/stop returned 404 for forwards owned by other nodes:
   findLinkByTriple only checked local SQLite store. Added
   findLinkFromTopology fallback — startForward/stopForward now look up
   ring topology entries when the local store doesn't have the link.

2. Group labels didn't sync through the ring:
   handleForwardsAssign only updated local SQLite, never the ring
   topology. Added State.UpdateTopologyGroup + Engine.UpdateTopologyGroup
   — handleForwardsAssign now updates both. Added topologySync callback
   called after every e.state = tk.State (OnToken) and e.state = s
   (AdoptState) to re-apply local store group overrides onto the freshly
   adopted topology, so they survive state adoption and propagate via
   the next token forward. handleForwardsGroupDelete also clears ring
   topology entries.

3. Status-merge branch omitted Group field:
   ring-only forwards always showed '未分组'. Added Group: t.Link.Group
   to the topology-merge forwardStatus.

Verified: 4-node cluster, 5 forwards, group assigned on node-a
propagates to all nodes within one token cycle; stop from non-owning
node succeeds (HTTP 200) and worker stops on the owning node.
This commit is contained in:
2026-08-19 21:52:28 +08:00
parent 465a86c755
commit 2552c14faf
4 changed files with 110 additions and 39 deletions

View File

@ -404,6 +404,20 @@ func (s *State) ForwardsOwnedBy(owner string) []*TopoEntry {
return out
}
// UpdateTopologyGroup sets the group label on the topology entry matching the
// (local, remote, port) triple. Returns true if found. The updated entry
// propagates to all nodes via the next token cycle — group changes sync
// through the ring without a dedicated command.
func (s *State) UpdateTopologyGroup(local, remote string, port int, group string) bool {
for _, e := range s.Topology {
if e.Local.Name == local && e.Remote.Name == remote && e.Link.RemotePort == port {
e.Link.Group = group
return true
}
}
return false
}
// OfflineReassign moves all active forwards owned by an offline node back into
// PendingTasks (they become new tasks for the next lowest-load member). Returns
// the reassigned task ids.