fix(NET): 监听重建单一所有者收口 + 沉降窗 800ms 上限只留痕
CI / L0 静态门禁 (pre-commit) (push) Failing after 1m12s
CI / L0.5 主机侧单测 (MSVC) (push) Successful in 17s

T0 降噪 + T1 单一所有者:四处重建触发(recon/accept/eof/drop)与 pending
第五处统一走 net_listener_request(),由 net_listener_tick 单点执行;
沉降重试日志改"只留首末"(E0 口径),每次恢复少打 3 行重复行。

force open 降级为只留痕(E19 反事实 A/B 实测):芯片未回 CLOSED 时强行
open 注定吃 USE 错(TRAP-NET-09),放行与不放行的端口恢复时刻同一分布
(放行 +512 / 不放行 +505·533 / 降级后 +509 ms)——恢复由芯片何时回
CLOSED 决定。真正兜住"监听器永久失效"的是 D10 预算到期 + 对账形态④
重新登记,不是放行。新增 s_relisten_capped[] 使上限留痕只打一次,
隔离标志不清 → 下一轮继续排空残渣(构建 B 实测每 30ms 两行会刷屏)。

负向注入三轮打靶(E18,注入代码不入库):形态① 吞掉 DISCONNECT 后
4/4 救回、③ 空闲监听器静默失位两轮独立复现、⑤ 开盲区吞掉芯片自发中断
+ 收回 LISTEN 后 -> resync 命中且挂起 recv 被取消 → 三条均不可降级为
只留痕。顺带稳定复现 [ERR] relisten retry budget exhausted = 对端不配合
拆链的签名,非回归。

台架:boot PASS;ftp_abort_probe RST 10/10 + FIN 5/5;relisten_stress
:21 50/50(p50 507ms);n30_repro 30/30。构建 0 错 0 警 Code=212316;
netTask 静态最坏链 1920→1912 B;RAM 台账受管区已 --write 同步(RW +8B)。
文档:E17/E18/E19 三行 + §4.1 常态 0 命中归因 + §4.3 兜底归属改正;
登记簿 N-31 销项(结论沉淀进 §4.3 与 E19)。
This commit is contained in:
2026-10-14 15:57:10 +08:00
parent e95d2be30b
commit 0edbfaf08b
17 changed files with 356 additions and 224 deletions
+30 -29
View File
@@ -780,11 +780,11 @@ static void net_reconcile_tick(void) {
/* 挂起 send 同样取消(会话已被芯片收走,剩余字节不可能送达) */
net_send_pending_cancel(i, NET_ERR_CONNRESET);
} else if ((bad == 3) || (bad == 4)) {
/* 类型③④:监听失位/孤儿监听 → 重建(本层唯一重建入口;
* 非阻塞:失败由 D10 重试轮询接管) */
/* 类型③④:监听失位/孤儿监听 → **登记**重建请求(判据在此,动作在
* net_listener_tick 一处;非阻塞,30ms 节奏重试) */
DBG_INFO("sock%d reconcile: chip not listening (0x%02X/0x%02X, was state=%d) -> relisten",
i, c.open, c.tcp_state, (int)p_sock->state);
(void)net_listener_try(i);
net_listener_request(i, NET_LISTENER_REQ_RECON);
} else {
/* 类型②:补做 standalone accept */
DBG_INFO("sock%d reconcile: connection completed w/o CONNECT irq -> accept", i);
@@ -806,8 +806,8 @@ static void net_reconcile_tick(void) {
* 限定条件:必须在 netTask 上下文中调用(独占 CH395F 与 s_net_socks 访问权);
* 由 net_task.c 在 net_stack_bring_up 之后每次 1ms 循环调用。
* 函数说明:1. 四个自愈 tick 均非阻塞(绝不 osDelay 忙等,netTask 永不冻结),先于消息分派执行
* 2. net_listener_retry_tick(D10):断连后按 30ms 节拍重试 open+listen,
* 2s 时限到期放弃留痕;本 fd 有在途挂起 recv / send 时跳过(挂起保险)
* 2. net_listener_tick(监听重建**唯一执行者**):消费各路径登记的请求,
* 30ms 节奏 / 2s 预算;门集与"芯片在听就不 open"(U12 防护)都在这一处
* 3. net_pending_tick:按节拍结束挂起 recv(数据/到点[仅显式预算]/断连)
* 4. net_send_pending_tick:按节拍推进挂起 send(断连/推进/写完/无进展超时)
* 5. net_reconcile_tick(TRAP-NET-17 + pump 合并,自愈收敛阶段 3):100ms 对账
@@ -825,8 +825,8 @@ void net_process_messages(void) {
}
/* 四个非阻塞自愈 tick 置于消息处理之前:断连后即使无应用消息,监听器也能自主恢复 */
/* D10 重监听重试(TRAP-NET-09):沉降后重开芯片监听器 */
net_listener_retry_tick();
/* 监听重建唯一执行者(TRAP-NET-09 D10 节奏;消费各路径登记的请求) */
net_listener_tick();
/* 挂起 recv 完成检查:数据到达 / 到点 / 断连时通知调用方 */
net_pending_tick(s_net_socks);
/* 挂起 send 推进与完成检查(SEND pending):断连 / 推进写块 / 写完 / 无进展超时 */
@@ -1516,13 +1516,13 @@ static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen
drain_pending = 1;
DBG_INFO("sock%d closed-with-data accept (drain pending)", sockfd);
} else {
/* 连接已断开(CLOSED)且无残留数据:先把 Socket 重新置回 LISTEN,
* 下一次 accept 即可等待新连接(BSD 风格监听常驻)。
* N-32 P2 修正:**保留本处即时重建** —— 它由消费者调 accept 触发
/* 连接已断开(CLOSED)且无残留数据:登记重建请求,下一次 accept
* 即可等待新连接(BSD 风格监听常驻)。
* N-32 P2 修正:**保留本处即时登记** —— 它由消费者调 accept 触发
* (消费者正在等新连接),不是周期性抢先;删掉会把 CLOSED 窗口
* 拉长、导致调用方高频空转 accept(实测 [WD] task stall)。
* 非阻塞:失败由 D10 重试轮询接管 */
(void)net_listener_try(sockfd);
* 单一所有者收口后:动作发生在下一轮 net_listener_tick(≤1ms),非阻塞 */
net_listener_request(sockfd, NET_LISTENER_REQ_ACCEPT);
}
}
if (drain_pending == 0) {
@@ -2155,20 +2155,20 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
* 有数据时走上方快路径(recv_len>0 优先),本判据只在"无数据"时生效,
* 故不会把新连接的起始数据误判为 EOF。 */
if ((p_sock->state == NET_SOCK_STATE_CLOSED) || (p_sock->session_dead != 0U)) {
/* 启用自动重监听:先把监听器重新置回 LISTEN。
* ⚠ 内层 `state == CLOSED` 门是**必需的**,不是冗余:
* `net_listener_try` 无状态门,对已 LISTENING 的 fd 再 open+listen
* 会让 ch395f_open_socket 返 USE,其失败分支会 ch395f_close_socket
* —— 把正听着新 SYN 的 Socket 当场关掉(U12 同源事故)。
* session_dead + 已回 LISTENING = 重建已由别处完成,这里只报 EOF。
* N-32 P2 修正(2026-10-12 台架实证):**保留本处即时重建**。
/* 启用自动重监听:登记重建请求(动作在 net_listener_tick 一处)。
* ⚠ 原此处内层 `state == CLOSED` 门已**上收到 tick**:它防的是"对已
* LISTENING 的 fd 再 open+listen → ch395f_open_socket 返 USE → 失败分支
* ch395f_close_socket → 把正听着新 SYN 的 Socket 当场关掉"(U12 同源
* 事故)。这是**执行侧安全不变量**,不该由每个登记点各自复述——
* 漏一处即事故;收口后 tick 另加"芯片已在听即了结",结构上不可能再犯。
* N-32 P2 修正(2026-10-12 台架实证):**保留本处即时登记**——
* 它是"消费者动作触发"(消费者正在等这条连接的结局),不是周期性
* "抢先";删掉它会把 CLOSED 窗口从 ~0 拉长到 pump 周期+隔离期
* "抢先";删掉它会把 CLOSED 窗口从 ~0 拉长到 reconciler 周期+隔离期
* (~100ms),期间调用方高频发起 net_accept(RPC 无上界)→
* `[WD] task stall`(实测 uis 往返 1063ms)。周期性抢先由 reconciler 收敛即可。 */
if ((p_sock->state == NET_SOCK_STATE_CLOSED) && (p_sock->auto_relisten != 0U)) {
/* 非阻塞:失败由 D10 重试轮询接管 */
(void)net_listener_try(sockfd);
* `[WD] task stall`(实测 uis 往返 1063ms)。周期性兜底由 reconciler 收敛。 */
if (p_sock->auto_relisten != 0U) {
/* 非阻塞:下一轮 net_listener_tick 执行,30ms 节奏重试 */
net_listener_request(sockfd, NET_LISTENER_REQ_EOF);
}
/* EOF:返回 0(非错误) */
return 0;
@@ -2247,9 +2247,10 @@ static int net_close_locked(net_sock_t *p_sock) {
* "拆连接不拆监听"的应用侧形态
* 2. 拆除=sock_reset QUARANTINE(TRAP-NET-18 异常释放语义:FIN + 排渣 +
* 排空事件(quiescent) + 等 byte0=00H,内置二段 CLOSE 补发)
* 3. auto_relisten(监听型 fd):收口 CLOSED 后即时 net_listener_try
* (芯片已 CLOSED 直接 open 必成,E4 定案;失败交 D10 重试 +
* reconciler 形态④);非 auto_relisten(client fd):收口 CLOSED,
* 3. auto_relisten(监听型 fd):收口 CLOSED 后登记重建请求(动作在
* net_listener_tick,≤1ms 后执行;芯片已 CLOSED 直接 open 必成,
* E4 定案;未成则 30ms 节奏重试 + reconciler 形态④兜底);
* 非 auto_relisten(client fd):收口 CLOSED,
* 是否重连由调用方决定
* 4. 挂起 recv 先结束(settle_close:排空尝试或取消,必通知)
*/
@@ -2275,8 +2276,8 @@ static int net_drop_conn_locked(net_sock_t *p_sock) {
p_sock->lost_conn_seen = 0U;
if (p_sock->auto_relisten) {
/* 监听型 fd:即时重建(非阻塞;失败 D10 重试轮询接管) */
(void)net_listener_try(sockfd);
/* 监听型 fd:登记重建请求(非阻塞;下一轮 net_listener_tick 执行) */
net_listener_request(sockfd, NET_LISTENER_REQ_DROP);
}
/* 拆除完成 */