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.1 KiB
Markdown
177 lines
8.1 KiB
Markdown
# 计划 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 逻辑都在同一文件中。
|
||
|
||
当前代码摘录:
|
||
|
||
```rust
|
||
// 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(())
|
||
}
|
||
```
|
||
|
||
```rust
|
||
// 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;
|
||
```
|
||
|
||
```rust
|
||
// 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_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_infos` changed flag tests:newer version true,same/older version false。
|
||
- 新增 `update_conn_info` changed 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 -- --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` 处理。
|