diff --git a/internal/cluster/ring_leader.go b/internal/cluster/ring_leader.go index 028305b..1c8cde5 100644 --- a/internal/cluster/ring_leader.go +++ b/internal/cluster/ring_leader.go @@ -155,7 +155,9 @@ func (e *Engine) forwardToNext(ctx context.Context, tk *Token) error { } // Non-leader neighbor death: continue to the next recipient. } - e.inflight.clear() + // 全环遍历完毕,所有后继都不可达(或已全部尝试过)。 + // 保持 inflight(不清空),让 WatchTokenLoss 超时后重发。 + // 重发时 AliveSuccessor 重新计算,可能已被心跳复活。 return nil } diff --git a/internal/httpapi/server.go b/internal/httpapi/server.go index f0a47da..824a6cb 100644 --- a/internal/httpapi/server.go +++ b/internal/httpapi/server.go @@ -9,6 +9,8 @@ import ( "fmt" "io" "io/fs" + "log" + "net" "net/http" "os" "path/filepath" @@ -538,11 +540,31 @@ func (h *Handler) handleClusterToken(w http.ResponseWriter, r *http.Request) { // predecessor's WatchLeader heartbeat fail → MarkOffline → becomeLeader // → StartRing, healing the ring. Per design: "心跳拒绝应当发生在leader // 退出节点时,让leader上邻居意识到当前环已经没有节点了". + // + // 同时,心跳到达说明前驱还活着,leader 应在地自己的拓扑中把它标记为 + // alive。否则 forwardToNext 一次超时把前驱 MarkOffline 后就再没有 + // 途径恢复它——前驱明明活着,leader 却认为它 offline → AliveSuccessor + // 找不到后继,令牌冻结在 leader 手上。心跳是 leader 侧纠正误判的唯一 + // 入口(因为心跳只有前驱发给 leader 这一条边)。 if r.Body == nil { if s := h.Ring.State(); len(s.Nodes) <= 1 { http.Error(w, "standalone node", http.StatusConflict) return } + // 心跳到达 = 前驱活着,标记 alive(纠正 forwardToNext 的误判) + if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { + s := h.Ring.State() + for i := range s.Nodes { + if h, _, e := net.SplitHostPort(s.Nodes[i].Addr); e == nil && h == host { + if !s.Nodes[i].Alive { + log.Printf("heartbeat from %s revives it (was offline)", s.Nodes[i].ID) + } + s.Nodes[i].Alive = true + s.Nodes[i].LastSeen = time.Now().Unix() + break + } + } + } w.WriteHeader(http.StatusOK) return } @@ -552,6 +574,20 @@ func (h *Handler) handleClusterToken(w http.ResponseWriter, r *http.Request) { http.Error(w, "standalone node", http.StatusConflict) return } + // 同上:心跳到达 = 前驱活着 + if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { + s := h.Ring.State() + for i := range s.Nodes { + if h, _, e := net.SplitHostPort(s.Nodes[i].Addr); e == nil && h == host { + if !s.Nodes[i].Alive { + log.Printf("heartbeat from %s revives it (was offline)", s.Nodes[i].ID) + } + s.Nodes[i].Alive = true + s.Nodes[i].LastSeen = time.Now().Unix() + break + } + } + } w.WriteHeader(http.StatusOK) return }