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).
8.1 KiB
计划 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.rseasytier/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_info 和 update_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_infoschanged flag tests:newer version true,same/older version false。 - 新增
update_conn_infochanged flag tests:changed topology true,identical 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 -- --checkexits 0。cargo clippy --all-targets --features full --all -- -D warningsexits 0。cargo nextest archive --archive-file tests.tar.zst --package easytier --features fullexits 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处理。