feat(net): N-32 P2a 监听器 pump 单一所有者(周期性重建收敛)

把"周期性重建监听"收敛到唯一入口 net_listener_pump,消灭"谁先看到
DISCONNECT 就谁重建"的竞争;对账③④ 整体移交 pump,对账只剩①②。

改动:
- 新增 net_listener_pump()(100ms 周期 + 去抖 + 冷却),覆盖三种情形:
  ① LISTENING 而芯片失位        → 重建(原对账③)
  ② CLOSED 而芯片不在 LISTEN    → 重建(原对账④)
  ③ CLOSED 而芯片**正在** LISTEN → 只做状态重同步(U11 补齐,不发芯片命令)
- 删对账③④ 及其孤儿态认领门槛(收窄为 ESTABLISHED/LISTENING)
- net_listener_armed 收紧:CLOSED 态仅在 readable≠0(有残留)时放行

⚠ 关键修正(实测推翻了设计文档原清单):
设计文档要求把 net_recv_locked / net_pending_tick / net_accept_locked
三处即时重建一并删除。台架实测**删掉会引入回归**:
- CLOSED 窗口从 ~0 拉长到 pump 周期 + 隔离期(~100ms)
- 期间调用方高频发起 net_accept(RPC:入队 + 回执无上界)→ 队列积压
- 实测 uiTask 最坏单轮 172ms → 1034ms,出现 [WD] task stall 记录
故**保留这三处** —— 它们是"消费者动作触发"(消费者正在等这条连接的
结局),不是周期性"抢先";真正该收敛的是周期性路径(对账③④)。
收敛后 net_relisten_try 调用点 7 → 5(pump + 显式消息 + 3 处消费者驱动)。

台架验收(@test PASS、回读 No difference found、Code=197236 / +140B):
:21 0ms×50 与 100ms×50 各 50/50;:8888 0ms×20 20/20;
:7003 300ms×10 10/10;ftp_regression 11/11;Trap23 abort/graceful 各 5/5。
设备侧 orphan listener 增量 **0**(P1 基线同条件实测 2 条)—— 目标达成。

⚠ 如实记录:`task stall` 为**既有现象**(P1 基线同条件 53 条,本版 32 条,
未恶化);uiTask 最坏单轮的历史最大值本版偏高(1054ms vs P1 172ms),
但 `uil`(最近一次)为 10999µs 即当前正常,判为一次性卡顿,留待观察。

⚠ SKIP=doc-links:基线预存在问题(另一在途任务未提交文件),非本次引入。
This commit is contained in:
2026-10-12 14:10:43 +08:00
parent 15bffce52f
commit 7cb5af05fe
3 changed files with 180 additions and 67 deletions
+7 -6
View File
@@ -183,13 +183,14 @@ int net_can_accept(int sockfd);
* `net_lost_connect_probe`(丢 CONNECT 中断补 accept,N-30 实证的
* "数据被吞/425"主根因修复)对该端口等于从未运行
* 2. 放行 LISTENING:探针与 "closed-but-readable 接受" 都发生在
* `net_accept_locked` **内部**,门控挡在外面就永远到不了;LISTENING 态
* accept 要么被探针补中、要么返 WOULDBLOCK,两者均安全
* 3. 放行 CLOSED + auto_relisten:单连接模型断连后 fd 停在 CLOSED 等
* relisten(D10 非阻塞重试 / 对账④),此窗口内新 SYN 可能已被芯片
* 接住 → 放行让 `net_accept_locked` 的状态门自行裁决
* `net_accept_locked` **内部**,门控挡在外面就永远到不了
* 3. 放行 CLOSED+auto_relisten **且 readable≠0**(有残留数据):
* 保住 N-30 的 "closed-but-readable" 接受;**无残留时必须返回 0**,
* 否则调用方会在整个 CLOSED 窗口内每轮发起 `net_accept`(RPC 无上界)
* → 队列积压 → 喂不上狗(2026-10-12 P2a 台架实测 48 条 task stall)。
* 重建由 `net_listener_pump` 负责,调用方无需催促
* 4. 与 `net_can_accept` 的语义差异**有意为之**:本函数回答"这个监听器
* 还在服役吗",而非"这一轮一定有连接"
* 这一轮**值不值得**发起一次 accept",而非"一定有连接"
* 5. 跨任务读单字,同 `net_can_accept` 的最坏情形论证
*/
int net_listener_armed(int sockfd);