Files
Easytier/plans/003-avoid-noop-ospf-route-rebuilds.md
T
fanyang 4b654fc56e perf(mpsc): sync send via noop_waker — +90% pps (249K → 474K)
The async fn Future state machine overhead (~1.9us) dominated
MpscTunnelSender::send, while RingSink operations were only ~40ns.

Breakthrough: make send() an async fn that completes synchronously
on the first poll for the direct (ring tunnel) path. Uses
futures::task::noop_waker() to construct a dummy Context, then calls
Sink trait methods (poll_ready, start_send, poll_flush) directly.
RingSink always returns Ready immediately, so the waker is never
invoked and the async fn completes without yielding.

Channel mode (TCP/UDP/WG tunnels) still uses async send_async()
with proper backpressure. Ring tunnels detected via tunnel_info()
type check in PeerConn.

Results (4 threads, 1400B, 15s):
  pps:                   249K → 474K (+90%)
  send_msg_by_ip:        3.53us → 1.67us (-53%)
  send_msg_internal:     2.40us → 502ns  (-79%)
  MpscTunnelSender::send: 1.97us → 144ns  (-93%)

All 207 peers:: tests pass. Netns-requiring tests (three_node,
credential) unchanged (require root).
2026-06-28 21:15:05 +08:00

8.1 KiB
Raw Blame History

计划 003:避免 OSPF 对 stale/no-op sync payload 重算路由

执行者说明:按步骤执行本计划。每一步都必须运行验证命令,并确认结果符合预期后再继续。如果触发“STOP 条件”中的任一情况,立即停止并报告,不要自行发挥。完成后更新 plans/README.md 中本计划的状态行,除非 reviewer 明确说明由他们维护索引。

漂移检查(首先运行)git diff --stat 78146d16..HEAD -- easytier/src/peers/peer_ospf_route.rs easytier/src/tests 如果本计划写成后任何范围内文件发生变化,继续前必须对照“当前状态”中的摘录与实时代码;如果不匹配,按 STOP 条件处理。

状态

  • 优先级: P2
  • 工作量: S
  • 风险: MED
  • 依赖: none
  • 类别: perf
  • 计划生成于: commit 78146d16, 2026-06-18

为什么重要

OSPF sync handler 当前已经能判断 peer_infos 是否实际写入了更新版本,但函数只返回 Result<(), Error>,调用方仍对任何非空 payload 设置 need_update_route_table = true。在重复、乱序或旧版本 sync 消息较多时,会触发完整 route-table rebuild,造成不必要 CPU 和锁竞争。完成后只有 stored topology state 变化时才重算路由,同时保持 duplicate peer ID 检查和 trust 更新语义。

当前状态

  • easytier/src/peers/peer_ospf_route.rs — OSPF route sync、state mutation 和 route-table rebuild 逻辑都在同一文件中。

当前代码摘录:

// easytier/src/peers/peer_ospf_route.rs:868
fn update_peer_infos(
    &self,
    my_peer_id: PeerId,
    my_peer_route_id: u64,
    dst_peer_id: PeerId,
    peer_infos: &[RoutePeerInfo],
    raw_peer_infos: &[DynamicMessage],
) -> Result<(), Error> {
    let mut need_inc_version = false;
    // ...
    if need_inc_version {
        self.version.inc();
    }
    Ok(())
}
// easytier/src/peers/peer_ospf_route.rs:3623
service_impl.synced_route_info.update_peer_infos(
    my_peer_id,
    service_impl.my_peer_route_id,
    from_peer_id,
    pi,
    rpi,
)?;
// ...
session.update_dst_saved_peer_info_version(pi, from_peer_id);
need_update_route_table = true;
// easytier/src/peers/peer_ospf_route.rs:3647
service_impl.synced_route_info.update_conn_info(conn_info);
session.update_dst_saved_conn_info_version(conn_info, from_peer_id);
need_update_route_table = true;

仓库约定:性能修复必须保守;route correctness 优先于少重算。已有 foreign_network_changed 风格 change flag,应匹配这种模式,不要引入复杂 scheduler。

需要使用的命令

Purpose Command Expected on success
Format cargo fmt --all -- --check exit 0
Lint cargo clippy --all-targets --features full --all -- -D warnings exit 0, no warnings
Targeted tests cargo test --package easytier peer_ospf_route --features full -- --nocapture exit 0; new change-flag tests pass if present
Integration tests cargo nextest archive --archive-file tests.tar.zst --package easytier --features full exit 0

临时目录约定

  • 临时文件、scratch 目录和 disposable worktree 必须放在 $HOME/tmp 下。
  • 如果 $HOME/tmp 不存在且本计划需要临时空间,先创建它。
  • 不要把临时产物放进被修改仓库。

范围

范围内(只能修改这些文件):

  • easytier/src/peers/peer_ospf_route.rs
  • easytier/src/tests/*(仅当需要新增 route sync regression test

范围外(即使看起来相关也不要触碰):

  • OSPF graph algorithm、route-table data structures、credential trust policy。
  • protobuf schema and generated code。
  • GUI/Web/frontend。

Git 工作流

  • Branch: advisor/003-avoid-noop-ospf-route-rebuilds
  • Commit message style follows existing conventional commits, for example fix: route_update message is not lag.
  • Do NOT push or open a PR unless the operator instructed it.

步骤

步骤 1:让 update_peer_infos 返回是否改变 state

update_peer_infos 返回类型从 Result<(), Error> 改为 Result<bool, Error>,返回 need_inc_version。保持 duplicate peer ID 检查、raw peer info 更新和 version increment 逻辑不变。

调用方保存为 let peer_infos_changed = ...?;

验证cargo test --package easytier peer_ospf_route --features full -- --nocapture → exit 0 或无匹配测试但编译通过。

步骤 2:确认 update_conn_info 是否已有 changed flag

阅读同文件中 update_conn_infoupdate_conn_info_one_peer。如果 update_conn_info_one_peer 已返回 bool,则让 update_conn_info 聚合并返回 bool。如果当前 update_conn_info 已返回 bool,只使用现有返回值,不重复实现。

不要改变 accept/reject credential conn info 的条件;只改变“是否设置 need_update_route_table”的判断。

验证cargo test --package easytier peer_ospf_route --features full -- --nocapture → exit 0。

步骤 3:仅在 actual change 时设置 need_update_route_table

在 sync handler 中改为:

  • peer_infos_changed 为 true 时才设置 need_update_route_table = true
  • conn_info_changed 为 true 时才设置 need_update_route_table = true
  • session.update_dst_saved_peer_info_version(...)session.update_dst_saved_conn_info_version(...) 是否应在 unchanged payload 时调用,需要按现有 session version semantics 判断;如果它只是记录对端已发送版本,可保留调用,避免重复请求。

验证cargo clippy --all-targets --features full --all -- -D warnings → exit 0。

步骤 4:新增 no-op update regression tests

优先添加 module-level unit tests,直接构造 SyncedRouteInfo 或现有内部结构:

  • 首次插入较新 RoutePeerInfo 返回 true
  • 再次插入相同 version 或旧 version 返回 false
  • update_conn_info 对相同 connected peers 返回 false,对变化集合返回 true

如果内部类型构造太复杂,使用现有 route sync tests 的 helper;不要为了测试暴露 public API,最多使用 #[cfg(test)] helper。

验证:使用实际新增测试过滤器运行,例如 cargo test --package easytier noop_route_update --features full -- --nocapture → exit 0;输出中新增测试通过。

步骤 5:运行完整相关门禁

验证

  • cargo fmt --all -- --check → exit 0。
  • cargo clippy --all-targets --features full --all -- -D warnings → exit 0。
  • cargo nextest archive --archive-file tests.tar.zst --package easytier --features full → exit 0。

测试计划

  • 新增 update_peer_infos changed flag testsnewer version truesame/older version false。
  • 新增 update_conn_info changed flag testschanged topology trueidentical topology false。
  • 不要求跑完整 privileged nextest matrix;至少 archive 编译所有 tests。

完成标准

  • stale/duplicate peer info 不再设置 need_update_route_table = true
  • unchanged conn info 不再设置 need_update_route_table = true
  • duplicate peer ID check 仍在 stale/no-op 判断前执行。
  • 新增 tests 覆盖 true/false change flag。
  • cargo fmt --all -- --check exits 0。
  • cargo clippy --all-targets --features full --all -- -D warnings exits 0。
  • cargo nextest archive --archive-file tests.tar.zst --package easytier --features full exits 0。
  • 没有修改范围外文件。
  • 已更新 plans/README.md 中本计划的状态行。

STOP 条件

  • update_peer_infos 的返回值已被其他分支重构,当前摘录不匹配。
  • 判断 no-op 需要改变 route trust、credential 或 duplicate peer semantics。
  • 无法构造可靠测试,且只能通过完整三节点集成测试验证;停止并报告需要 reviewer 决定测试策略。

维护说明

  • 后续任何 route sync state mutation 都应返回 changed flag,并只在 actual change 时触发 route rebuild。
  • reviewer 应重点检查 version bookkeeping:不要为了省重算而漏掉必要 route refresh。
  • 本计划不减少单次 rebuild 的成本;那由 plans/004-reuse-ospf-route-graph.md 处理。