mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-09-02 17:15:43 +00:00
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).
177 lines
8.7 KiB
Markdown
177 lines
8.7 KiB
Markdown
# 计划 004:复用 OSPF route-table 构图以减少拓扑更新成本
|
||
|
||
> **执行者说明**:按步骤执行本计划。每一步都必须运行验证命令,并确认结果符合预期后再继续。如果触发“STOP 条件”中的任一情况,立即停止并报告,不要自行发挥。完成后更新 `plans/README.md` 中本计划的状态行,除非 reviewer 明确说明由他们维护索引。
|
||
>
|
||
> **漂移检查(首先运行)**:`git diff --stat 78146d16..HEAD -- easytier/src/peers/peer_ospf_route.rs easytier/src/tests`
|
||
> 如果本计划写成后任何范围内文件发生变化,继续前必须对照“当前状态”中的摘录与实时代码;如果不匹配,按 STOP 条件处理。
|
||
|
||
## 状态
|
||
|
||
- **优先级**: P2
|
||
- **工作量**: M
|
||
- **风险**: MED
|
||
- **依赖**: plans/003-avoid-noop-ospf-route-rebuilds.md
|
||
- **类别**: perf
|
||
- **计划生成于**: commit `78146d16`, 2026-06-18
|
||
|
||
## 为什么重要
|
||
|
||
每次 OSPF 拓扑更新当前会分别为 least-hop 和 least-cost route table 调用 `build_from_synced_info`。每次调用都会从 synced info 重新构建 peer graph,并重新构建 peer/CIDR indexes。对于 peer 数和 proxy CIDR 数较大的 mesh,这把一次拓扑变化放大成两次完整构图和多次 map/trie 重建。完成后应保持 route selection 结果不变,同时复用同一份 graph/materialized synced view,减少 CPU 和分配成本。
|
||
|
||
## 当前状态
|
||
|
||
- `easytier/src/peers/peer_ospf_route.rs` — `update_route_table`、graph builder、least-hop/least-cost map generation、CIDR trie rebuild 均在此文件。
|
||
|
||
当前代码摘录:
|
||
|
||
```rust
|
||
// easytier/src/peers/peer_ospf_route.rs:1628
|
||
// build next hop map
|
||
let (graph, start_node) =
|
||
Self::build_peer_graph_from_synced_info(my_peer_id, synced_info, cost_calc);
|
||
|
||
// easytier/src/peers/peer_ospf_route.rs:1649
|
||
if matches!(policy, NextHopPolicy::LeastHop) {
|
||
self.gen_next_hop_map_with_least_hop(&graph, &start_node, version);
|
||
} else {
|
||
self.gen_next_hop_map_with_least_cost(&graph, &start_node, version);
|
||
};
|
||
|
||
// easytier/src/peers/peer_ospf_route.rs:1655
|
||
let mut new_cidr_prefix_trie = PrefixMap::new();
|
||
let mut new_cidr_v6_prefix_trie = PrefixMap::new();
|
||
```
|
||
|
||
```rust
|
||
// easytier/src/peers/peer_ospf_route.rs:2453
|
||
fn update_route_table(&self) {
|
||
// ...
|
||
self.route_table.build_from_synced_info(
|
||
self.my_peer_id,
|
||
&self.synced_route_info,
|
||
NextHopPolicy::LeastHop,
|
||
calc_locked.as_ref().unwrap(),
|
||
);
|
||
|
||
self.route_table_with_cost.build_from_synced_info(
|
||
self.my_peer_id,
|
||
&self.synced_route_info,
|
||
NextHopPolicy::LeastCost,
|
||
calc_locked.as_ref().unwrap(),
|
||
);
|
||
}
|
||
```
|
||
|
||
仓库约定:core routing behavior must be preserved。先添加 characterization tests,再重构;不要在同一计划里改 route policy。
|
||
|
||
## 需要使用的命令
|
||
|
||
| 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; route characterization tests pass |
|
||
| Archive 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-table characterization tests)
|
||
|
||
**范围外**(即使看起来相关也不要触碰):
|
||
- Route protocol schema and wire format。
|
||
- Credential/trust semantics。
|
||
- Peer center、foreign network manager、data-plane tunnels。
|
||
- Any UI or config surface。
|
||
|
||
## Git 工作流
|
||
|
||
- Branch: `advisor/004-reuse-ospf-route-graph`
|
||
- Commit message style follows existing conventional commits, for example `refactor: introduce HedgeExt for task hedging; rewrite NatDstQuicConnector`.
|
||
- Do NOT push or open a PR unless the operator instructed it.
|
||
|
||
## 步骤
|
||
|
||
### 步骤 1:添加 route-table characterization tests
|
||
|
||
在修改实现前,新增测试覆盖至少一个包含以下元素的小拓扑:
|
||
|
||
- 本 peer、两个 reachable peers、一个 unreachable 或 outdated peer。
|
||
- 至少一个 IPv4 proxy CIDR 和一个 IPv6 proxy CIDR。
|
||
- least-hop 和 least-cost 结果不同或至少都被断言。
|
||
|
||
测试应断言当前 `route_table` 和 `route_table_with_cost` 对 peer next-hop、peer reachability、CIDR lookup 的结果。优先使用现有测试 helper;如果内部 API 不便,添加 `#[cfg(test)]` helper,不改变生产 API。
|
||
|
||
**验证**:`cargo test --package easytier peer_ospf_route --features full -- --nocapture` → exit 0;新增 characterization tests 在重构前通过。
|
||
|
||
### 步骤 2:抽出一次性 graph build 输入
|
||
|
||
在 `peer_ospf_route.rs` 中把 `build_from_synced_info` 内部的 graph construction 拆成私有 helper,例如:
|
||
|
||
- `build_peer_graph_from_synced_info(...)` 已存在则复用。
|
||
- 新增 small struct 持有 `graph`、`start_node`、`version` 和后续 index rebuild 需要的 synced snapshot references。
|
||
|
||
不要改变 `gen_next_hop_map_with_least_hop` 或 `gen_next_hop_map_with_least_cost` 的算法。
|
||
|
||
**验证**:`cargo test --package easytier peer_ospf_route --features full -- --nocapture` → exit 0。
|
||
|
||
### 步骤 3:让 `update_route_table` 对两种 policy 复用 graph
|
||
|
||
把 `update_route_table` 改为在持有 `cost_calculator` read lock 时构建一次 graph/materialized input,然后分别对 `self.route_table` 和 `self.route_table_with_cost` 应用 least-hop / least-cost generation。
|
||
|
||
如果现有 `RouteTable::build_from_synced_info` 是唯一封装点,可以新增一个 sibling method,例如 `build_from_prebuilt_graph(...)`,保持旧方法用于兼容 tests 或其他调用方。
|
||
|
||
**验证**:`cargo test --package easytier peer_ospf_route --features full -- --nocapture` → exit 0;characterization tests 仍通过。
|
||
|
||
### 步骤 4:避免重复构建共享 indexes
|
||
|
||
检查 `build_from_synced_info` 中 peer info map、IPv4 map、CIDR tries 的生成是否依赖 policy-specific next-hop map。如果只依赖 reachability 或 synced info,可移动到共享 helper;如果依赖每个 `RouteTable` 自己的 `next_hop_map`,不要强行共享,避免改变 semantics。
|
||
|
||
允许分阶段收益:只共享 graph build 也可完成本计划;共享 CIDR/index 只有在 characterization tests 能证明 behavior 不变时才做。
|
||
|
||
**验证**:`cargo test --package easytier peer_ospf_route --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。
|
||
|
||
## 测试计划
|
||
|
||
- 新增 route-table characterization tests,先在重构前证明现有行为,再在重构后保持通过。
|
||
- 测试覆盖 least-hop、least-cost、CIDR lookup、unreachable peer exclusion。
|
||
- 如果可行,加入一个轻量 counter/helper 在 test-only path 确认 graph builder 调用次数从 2 降为 1;如果这需要侵入生产代码,则不要做。
|
||
|
||
## 完成标准
|
||
|
||
- [ ] `update_route_table` 不再对同一 synced topology 构建两次 peer graph。
|
||
- [ ] least-hop 和 least-cost route outputs 与 characterization tests 中的旧行为一致。
|
||
- [ ] 没有改变 routing protocol、credential trust 或 config behavior。
|
||
- [ ] `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 条件
|
||
|
||
- `plans/003-avoid-noop-ospf-route-rebuilds.md` 未完成,且当前 route rebuild trigger 仍会对 no-op payload 重算。
|
||
- 复用 graph 需要改变 least-hop 或 least-cost algorithm。
|
||
- 现有代码让 `cost_calc` 在两次 build 之间发生有意状态变化;如果确认 `begin_update`/`end_update` 依赖两次独立 build,停止并报告。
|
||
- Characterization tests 无法稳定构造 route-table expected outputs。
|
||
|
||
## 维护说明
|
||
|
||
- reviewer 应重点审查是否在锁持有期间引入更长 critical section。
|
||
- 未来新增 route policy 时应复用本计划抽出的 prebuilt graph input,而不是再调用完整 `build_from_synced_info`。
|
||
- 本计划不拆分 `peer_ospf_route.rs` 大文件;只做局部性能重构。
|