Files
Easytier/plans/002-bound-peer-rpc-queues.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

161 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 计划 002:为 peer RPC/control packet 队列加入背压和过载行为
> **执行者说明**:按步骤执行本计划。每一步都必须运行验证命令,并确认结果符合预期后再继续。如果触发“STOP 条件”中的任一情况,立即停止并报告,不要自行发挥。完成后更新 `plans/README.md` 中本计划的状态行,除非 reviewer 明确说明由他们维护索引。
>
> **漂移检查(首先运行)**`git diff --stat 78146d16..HEAD -- easytier/src/peers/peer_manager.rs easytier/src/peers/foreign_network_manager.rs easytier/src/common/stats_manager.rs easytier/src/tests`
> 如果本计划写成后任何范围内文件发生变化,继续前必须对照“当前状态”中的摘录与实时代码;如果不匹配,按 STOP 条件处理。
## 状态
- **优先级**: P1
- **工作量**: M
- **风险**: MED
- **依赖**: plans/001-thread-safe-metrics-throughput.md
- **类别**: perf
- **计划生成于**: commit `78146d16`, 2026-06-18
## 为什么重要
Peer RPC/control packet transport 当前使用 `mpsc::unbounded_channel()`network-facing packet processor 对每个 RPC packet 直接 `send(...).unwrap()`。如果远端或本地 relay 突发控制面 packet,队列可以无限增长,导致内存膨胀和控制面延迟;如果 receiver 关闭,`unwrap()` 还会 panic。完成后应有明确 bounded capacity、drop/backpressure policy 和可观测 drop 计数。
## 当前状态
- `easytier/src/peers/peer_manager.rs` — local peer RPC transport 队列和 packet processor。
- `easytier/src/peers/foreign_network_manager.rs` — foreign-network RPC transport 队列和 relay/local packet ingestion。
- `easytier/src/common/stats_manager.rs` — 如果 001 已完成,应复用线程安全 metrics 记录 queue drops。
当前代码摘录:
```rust
// easytier/src/peers/peer_manager.rs:275
// TODO: remove these because we have impl pipeline processor.
let (peer_rpc_tspt_sender, peer_rpc_tspt_recv) = mpsc::unbounded_channel();
```
```rust
// easytier/src/peers/peer_manager.rs:1245
struct PeerRpcPacketProcessor {
peer_rpc_tspt_sender: UnboundedSender<ZCPacket>,
}
// easytier/src/peers/peer_manager.rs:1257
self.peer_rpc_tspt_sender.send(packet).unwrap();
```
```rust
// easytier/src/peers/foreign_network_manager.rs:362
let (rpc_transport_sender, peer_rpc_tspt_recv) = mpsc::unbounded_channel();
// easytier/src/peers/foreign_network_manager.rs:529
rpc_sender.send(zc_packet).unwrap();
```
仓库约定:control-plane errors 通常通过 `tracing::{debug,warn,error}` 记录;packet hot path 应避免 blocking await。已有 data-plane queues elsewhere 倾向显式容量和丢弃策略;本计划应保持 hot path 非阻塞。
## 需要使用的命令
| 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 |
| Feature check | `cargo hack check --package easytier --each-feature --exclude-features macos-ne --verbose` | exit 0 |
| Targeted tests | `cargo test --package easytier peer_manager --features full -- --nocapture` | exit 0; new queue tests pass if present |
## 临时目录约定
- 临时文件、scratch 目录和 disposable worktree 必须放在 `$HOME/tmp` 下。
- 如果 `$HOME/tmp` 不存在且本计划需要临时空间,先创建它。
- 不要把临时产物放进被修改仓库。
## 范围
**范围内**(只能修改这些文件):
- `easytier/src/peers/peer_manager.rs`
- `easytier/src/peers/foreign_network_manager.rs`
- `easytier/src/common/stats_manager.rs`(仅用于添加/复用 drop metric names;不要重做 001
- `easytier/src/tests/*`(仅新增/调整本计划相关测试)
**范围外**(即使看起来相关也不要触碰):
- RPC protocol message definitions and generated protobuf code。
- Routing semantics、credential trust、foreign network topology logic。
- Frontend, web server, GUI。
## Git 工作流
- Branch: `advisor/002-bound-peer-rpc-queues`
- 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:定义 bounded capacity 和 overload policy
在两个文件中引入同一个小常量,建议名称为 `PEER_RPC_PACKET_QUEUE_CAPACITY`,初始值建议 `1024``4096`。如果已有相近 queue capacity 常量,复用仓库风格。
Policy 必须明确:packet hot path 不等待;当队列满或 receiver closed 时,丢弃当前 RPC/control packet,记录 `tracing::warn!` 或 rate-limited debug,并增加 drop counter。不要 panic。
**验证**`cargo fmt --all -- --check` → exit 0。
### 步骤 2:替换 `peer_manager.rs` 的 unbounded channel
`mpsc::unbounded_channel()` 替换为 `mpsc::channel(PEER_RPC_PACKET_QUEUE_CAPACITY)`。更新 `RpcTransport``PeerRpcPacketProcessor` 字段类型,从 `UnboundedSender`/unbounded receiver 改成 bounded `Sender`/`Receiver`
`try_process_packet_from_peer` 中不要 `.await`,使用 `try_send(packet)`。如果 `Full``Closed`,记录并返回 `None`,保持原有“这是 RPC packet,不再进入 data-plane pipeline”的行为。
**验证**`cargo test --package easytier peer_manager --features full -- --nocapture` → exit 0;如果没有匹配测试,至少必须编译通过。
### 步骤 3:替换 `foreign_network_manager.rs` 的 unbounded channel
同样将 foreign-network RPC transport 改为 bounded channel,并在 ingestion path 使用 `try_send(zc_packet)`。不得保留 `unwrap()`
如果两个文件都需要相同 helper,优先在各文件内保持小函数,避免为了复用引入新模块。最小正确改动优先。
**验证**`cargo test --package easytier foreign_network_manager --features full -- --nocapture` → exit 0;如果没有匹配测试,至少必须编译通过。
### 步骤 4:添加队列满/receiver closed 的单元测试或小型回归测试
尽量在模块内新增不依赖真实网络 namespace 的测试:创建 bounded channel 容量为 1,填满后调用封装的 send helper,断言不会 panic 且返回/drop counter 行为正确。如果代码结构不允许直接测试 private helper,可以抽出一个 file-local helper function,例如 `try_enqueue_rpc_packet(...) -> bool`,测试 helper。
不要为了测试启动完整三节点网络;这属于慢集成测试,不适合验证 queue behavior。
**验证**`cargo test --package easytier peer_rpc_queue --features full -- --nocapture` → exit 0;如果测试名不同,使用实际新增测试过滤器,输出中新增测试通过。
### 步骤 5:运行完整相关门禁
**验证**
- `cargo fmt --all -- --check` → exit 0。
- `cargo clippy --all-targets --features full --all -- -D warnings` → exit 0。
- `cargo hack check --package easytier --each-feature --exclude-features macos-ne --verbose` → exit 0。
## 测试计划
- 新增 queue helper tests,覆盖队列未满、队列满、receiver closed 三种情况。
- 如果添加 drop metric,测试满队列时 counter 增加。
- 不要求新增 full network integration testbounded queue behavior 应在 unit-level 可验证。
## 完成标准
- [ ] `peer_manager.rs` 不再为 peer RPC transport 使用 `mpsc::unbounded_channel()`
- [ ] `foreign_network_manager.rs` 不再为 foreign-network RPC transport 使用 `mpsc::unbounded_channel()`
- [ ] 相关 packet enqueue path 不再调用 `.unwrap()`
- [ ] 满队列和 receiver closed 有明确非 panic 行为。
- [ ] 新增或更新测试覆盖 queue overload behavior。
- [ ] `cargo fmt --all -- --check` exits 0。
- [ ] `cargo clippy --all-targets --features full --all -- -D warnings` exits 0。
- [ ] `cargo hack check --package easytier --each-feature --exclude-features macos-ne --verbose` exits 0。
- [ ] 没有修改范围外文件。
- [ ] 已更新 `plans/README.md` 中本计划的状态行。
## STOP 条件
- 001 尚未完成,而本计划需要新增 metrics/drop counters;此时先执行 001 或报告阻塞。
- `PeerRpcManager` 或 transport trait 要求 unbounded receiver 类型且无法在范围内替换。
- 正确实现需要改变 RPC protocol semantics 或 routing trust logic。
- bounded queue 导致现有 integration tests 稳定失败,且不能通过容量或 policy 微调解决。
## 维护说明
- reviewer 应重点审查 drop policy 是否适合 control-plane:丢弃低优先级 sync packet 可以接受,但不能默默破坏必须可靠的 request/response path。
- 后续如果出现 reconnect storm 或 route sync loss,应结合 drop metrics 调整 capacity。
- 本计划不实现优先级队列;如果未来需要区分 `RpcReq``RpcResp``TaRpc` 优先级,应另写计划。