mirror of
https://gitcode.com/JianFeeeee/webui4frpc.git
synced 2026-09-20 00:47:57 +00:00
feat(M6): token-ring incremental log sync — append-only op log, delta rides token round-2, nodes converge on identical history (e2e verified join events)
This commit is contained in:
@ -16,9 +16,14 @@ import (
|
||||
)
|
||||
|
||||
// LossTimeout returns the token-loss threshold per the authoritative design:
|
||||
// roundDelay/2 + 20ms.
|
||||
// roundDelay/2 + 20ms, floored at 600ms so a healthy fast ring is never
|
||||
// misjudged as lost.
|
||||
func LossTimeout(roundDelay time.Duration) time.Duration {
|
||||
return roundDelay/2 + 20*time.Millisecond
|
||||
t := roundDelay/2 + 20*time.Millisecond
|
||||
if t < 600*time.Millisecond {
|
||||
return 600 * time.Millisecond
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// inFlight tracks token-in-flight state (leader only).
|
||||
@ -65,8 +70,9 @@ func (f *inFlight) lastDelay() time.Duration {
|
||||
}
|
||||
|
||||
// flipIfComplete promotes the collect round to the sync round when every alive
|
||||
// node has been stamped; at the end of sync it starts a fresh cycle. Only the
|
||||
// leader flips.
|
||||
// node has been stamped; at the end of sync it completes the cycle. Only the
|
||||
// leader flips. Passed carries the set of nodes that saw this token this round;
|
||||
// it is NOT reset between phases (only between cycles).
|
||||
func (e *Engine) flipIfComplete(tk *Token) {
|
||||
if e.state.LeaderID != e.ID {
|
||||
return
|
||||
@ -74,14 +80,15 @@ func (e *Engine) flipIfComplete(tk *Token) {
|
||||
switch tk.Phase {
|
||||
case PhaseCollect:
|
||||
if allAlivePassed(&e.state, tk.Passed) {
|
||||
// Round 1 done: go to round 2 (sync). Round-2 stamping restarts
|
||||
// from the leader so the token walks the ring once more.
|
||||
tk.Phase = PhaseSync
|
||||
tk.Passed = nil
|
||||
tk.Passed = []string{e.ID}
|
||||
e.state.RoundDelay = tkDelaySince(tk)
|
||||
}
|
||||
case PhaseSync:
|
||||
if allAlivePassed(&e.state, tk.Passed) {
|
||||
// cycle done: start next collect round.
|
||||
e.state.Cycle++
|
||||
// Cycle done: leader holds token; next StartRing bumps cycle.
|
||||
e.state.RoundDelay = tkDelaySince(tk)
|
||||
}
|
||||
}
|
||||
@ -162,10 +169,33 @@ func (e *Engine) WatchLeader(ctx context.Context) {
|
||||
return
|
||||
case <-tick.C:
|
||||
succ, ok := e.state.AliveSuccessor(e.ID)
|
||||
if !ok {
|
||||
if !ok || succ == e.ID {
|
||||
continue // single-node ring: nothing to monitor
|
||||
}
|
||||
// Only the leader's predecessor (the previous alive node) monitors
|
||||
// it; a node whose successor is the leader is the predecessor.
|
||||
if e.state.LeaderID != succ {
|
||||
continue
|
||||
}
|
||||
if e.state.LeaderID != succ {
|
||||
pred, ok := e.state.AlivePredecessor(e.ID)
|
||||
if !ok || pred == e.ID {
|
||||
continue
|
||||
}
|
||||
// We monitor the leader only if we ARE its predecessor.
|
||||
if succ != e.state.LeaderID {
|
||||
continue
|
||||
}
|
||||
// pred must be us: leader's predecessor sends the heartbeat.
|
||||
if e.state.Nodes[e.state.Find(succ)].ID == e.ID {
|
||||
continue
|
||||
}
|
||||
if e.state.Find(e.ID) == -1 {
|
||||
continue
|
||||
}
|
||||
// This node is the leader's predecessor iff leader's predecessor
|
||||
// (alive) equals our ID.
|
||||
leaderPred, ok := e.state.AlivePredecessor(e.state.LeaderID)
|
||||
if !ok || leaderPred != e.ID {
|
||||
continue
|
||||
}
|
||||
if e.send != nil {
|
||||
|
||||
Reference in New Issue
Block a user