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.8 KiB
计划 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。
当前代码摘录:
// 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();
// 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();
// 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.rseasytier/src/peers/foreign_network_manager.rseasytier/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 test;bounded 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 -- --checkexits 0。cargo clippy --all-targets --features full --all -- -D warningsexits 0。cargo hack check --package easytier --each-feature --exclude-features macos-ne --verboseexits 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优先级,应另写计划。