Bug 描述
Raft 选举投票处理函数 NodeImpl::handle_request_vote_request 中存在竞态条件,导致本应被授予的合法投票被错误拒绝,可能引发选举延迟或失败。
复现条件
当以下事件序列发生时触发:
节点 A 发起选举,elect_self 中将 term 提升至 T+1,并向其他节点发送 RequestVoteRequest(term=T+1)
节点 B 收到该投票请求,进入 handle_request_vote_request,此时 _current_term = T,saved_current_term = T
节点 B 释放 _mutex(为获取 last_log_id,涉及阻塞 I/O)
在 unlock 窗口期间,节点 A 当选为 leader 后发送心跳(AppendEntries),心跳携带 term=T+1
节点 B 收到心跳,调用 check_term -> step_down,将 _current_term 提升至 T+1
节点 B 重新获取 _mutex,执行 ABA 检查:saved_current_term(T) != _current_term(T+1)
原逻辑直接 break,拒绝了这次投票
根因分析
关键代码路径
投票处理(node.cpp handle_request_vote_request):
心跳中 term 的来源(replicator.cpp _send_heartbeat):
Replicator 在发送心跳时通过 bthread_id_lock 获取独立锁,从 _options.term 读取 term。当 leader 选举成功后 _options.term 已提升至 T+1,心跳因此携带更高的 term。
心跳处理中的 term 推进(node.cpp handle_append_entries_request):
时序图
本质问题
原 ABA 检查过于保守:只要 _current_term 发生变化就无条件拒绝投票。但 _current_term 是单调递增的,真正的 ABA(A->B->A 回退)不可能发生。实际上这是一个 TOCTOU(Time of Check, Time of Use)问题。
当 term 从 T 变为 T+1,恰好等于 request->term()(T+1)时,投票请求仍然是合法的——候选人请求的 term 与当前 term 一致,应继续正常的投票判断流程。
影响
合法投票被拒绝,可能导致候选人无法获得多数票,触发选举超时重试
在网络抖动或心跳与投票并发频繁的场景下,可能反复出现,导致选举延迟
不会造成数据不一致(投票被拒是安全的),但影响可用性
修复方案
细化 ABA 检查逻辑,根据 _current_term 变化后的值与 request->term()
Bug 描述
Raft 选举投票处理函数 NodeImpl::handle_request_vote_request 中存在竞态条件,导致本应被授予的合法投票被错误拒绝,可能引发选举延迟或失败。
复现条件
当以下事件序列发生时触发:
节点 A 发起选举,elect_self 中将 term 提升至 T+1,并向其他节点发送 RequestVoteRequest(term=T+1)
节点 B 收到该投票请求,进入 handle_request_vote_request,此时 _current_term = T,saved_current_term = T
节点 B 释放 _mutex(为获取 last_log_id,涉及阻塞 I/O)
在 unlock 窗口期间,节点 A 当选为 leader 后发送心跳(AppendEntries),心跳携带 term=T+1
节点 B 收到心跳,调用 check_term -> step_down,将 _current_term 提升至 T+1
节点 B 重新获取 _mutex,执行 ABA 检查:saved_current_term(T) != _current_term(T+1)
原逻辑直接 break,拒绝了这次投票
根因分析
关键代码路径
投票处理(node.cpp handle_request_vote_request):
心跳中 term 的来源(replicator.cpp _send_heartbeat):
Replicator 在发送心跳时通过 bthread_id_lock 获取独立锁,从 _options.term 读取 term。当 leader 选举成功后 _options.term 已提升至 T+1,心跳因此携带更高的 term。
心跳处理中的 term 推进(node.cpp handle_append_entries_request):
时序图
本质问题
原 ABA 检查过于保守:只要 _current_term 发生变化就无条件拒绝投票。但 _current_term 是单调递增的,真正的 ABA(A->B->A 回退)不可能发生。实际上这是一个 TOCTOU(Time of Check, Time of Use)问题。
当 term 从 T 变为 T+1,恰好等于 request->term()(T+1)时,投票请求仍然是合法的——候选人请求的 term 与当前 term 一致,应继续正常的投票判断流程。
影响
合法投票被拒绝,可能导致候选人无法获得多数票,触发选举超时重试
在网络抖动或心跳与投票并发频繁的场景下,可能反复出现,导致选举延迟
不会造成数据不一致(投票被拒是安全的),但影响可用性
修复方案
细化 ABA 检查逻辑,根据 _current_term 变化后的值与 request->term()