Files
webui4frpc/cmd
JianFeeeee bfd7265405 fix(cluster): 重入成功后若自己是 leader 必须启动令牌环,否则集群冻结
上一提交(50c7729)部署后环**仍卡死**,实测 cycle 停在 458198、topology=0、
pending=3。查 .30 日志定位到第二个、也是真正让集群不动的原因:

    ring[.30] rejoined cluster via cached peer 192.168.2.60:7500
    ring[.30] startup reconcile: reclaimed=0 submitted=3
    (此后再无任何 forward cycle / token-send —— 令牌从未转过)

## 根因

retryRejoinCached 在 JoinRing 成功后**直接 return**,把"环会继续转"当作前提。
但 StartRing 只对 leader 生效,而滚动重启恰恰让 leader 自己重入(leader 最后
重启)。此时没有任何节点会转令牌 —— 非 leader 的 StartRing 是 no-op。

结果:leader 重入成功却从不发令牌 → cycle 冻结 → 那三条 pending 转发永远无人
认领 → topology 一直是 0。这与设计文稿 §容错行为不符(环应继续推进)。

## 修法

重入成功后调用 `go ring.StartRing(ctx)`。对非 leader 是 no-op,且内部按
RoundDelay 自节流,多调不会重发,故可无条件调用。

## 测试

TestRejoinThenStartRingCoversTheRejoinPath:用 httptest 起一个假 peer,端到端跑
真正的 retryRejoinCached,断言重入成功后 leader 确实发出了令牌。

★ 这条测试重写过两次才有效:
1. 第一版直接调 `eng.StartRing(...)` —— **假绿**,把修复自己的 helper 当成被测
   对象,回退修复行后仍然全绿(已实测确认)。
2. 第二版改走 retryRejoinCached,但传入 httptest 的完整 URL;JoinRing 自己拼
   "http://<addr>/api/manager/cluster/join",于是地址变成
   `http://http://127.0.0.1:PORT/...` 而永远打不通。改为裸 host:port。

双向验证:移除修复行后如期变红
("after a successful rejoin the leader emitted NO token: the ring is frozen"),
还原后变绿。

go build / go vet / go test ./... 全绿,gofmt 干净。
2026-10-05 17:34:57 +08:00
..