From 0b0c854650c26abe2fd706ebb2b92e78306cc70a Mon Sep 17 00:00:00 2001 From: bang <3656828039@qq.com> Date: Thu, 21 May 2026 20:07:36 +0800 Subject: [PATCH] =?UTF-8?q?fix(raft):=20electionLoop=20=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=20timer,=20=E6=8C=89=20Stop=E2=86=92drain=E2=86=92Reset=20?= =?UTF-8?q?=E9=A1=BA=E5=BA=8F=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 之前每次循环 time.NewTimer, 既浪费 GC, 又使 heartbeatCh/electionCh 触发时新随机的 timeout 跟旧的 timeout 不一致, 偏离 Raft 规范 (election 超时应在每轮 election 结束后重新选取, 而不是每次心跳)。 - NewRaftWithDataDir 内 time.NewTimer 一次, 用 r.electionTimeout - electionLoop 仅 select; 抽出 resetElectionTimer 用标准 Stop→drain→Reset 三步 - timeout 在每次 reset 时重新随机, 既保留原行为又避免 timer leak Co-Authored-By: Claude Opus 4.7 (1M context) --- Raft/raft.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Raft/raft.go b/Raft/raft.go index 6b43a67..8c10482 100644 --- a/Raft/raft.go +++ b/Raft/raft.go @@ -121,6 +121,7 @@ func NewRaftWithDataDir(peers []string, me int, dataDir string) *Raft { } r.commitCond = sync.NewCond(&r.mu) + r.timer = time.NewTimer(r.electionTimeout) go r.electionLoop() @@ -178,18 +179,28 @@ func (r *Raft) Start() { func (r *Raft) electionLoop() { for { - timeout := MinElectionTimeout + time.Duration(rand.Int63n(int64(MaxElectionTimeout-MinElectionTimeout))) - r.timer = time.NewTimer(timeout) - select { case <-r.timer.C: r.startElection() + r.resetElectionTimer() case <-r.heartbeatCh: - r.timer.Reset(timeout) + r.resetElectionTimer() case <-r.electionCh: - r.timer.Reset(timeout) + r.resetElectionTimer() + } + } +} + +// resetElectionTimer 复用单一 timer: 先 Stop→drain 残留信号, 再用新随机超时 Reset +func (r *Raft) resetElectionTimer() { + if !r.timer.Stop() { + select { + case <-r.timer.C: + default: } } + r.electionTimeout = MinElectionTimeout + time.Duration(rand.Int63n(int64(MaxElectionTimeout-MinElectionTimeout))) + r.timer.Reset(r.electionTimeout) } func (r *Raft) startElection() {