修复Trap22快速复连丢中断致监听死亡:net层周期对账自愈(去抖+冷却),复现用例与回归全绿

This commit is contained in:
2026-09-23 02:27:10 +08:00
parent 3ac8eb807d
commit 77ee778605
3 changed files with 146 additions and 18 deletions
+1 -1
View File
@@ -12,7 +12,7 @@
| N-1 | **✅ 已结案(2026-09-23):TC-NET-604"接收缓冲填满后 RX 窗口冻结"判定为非缺陷**——原报告(阶段 8:前序突发后 4096B 突发仅前 2KB 回显、其余无回显)在当前固件**不可复现**:真实生产路径(产品固件 + 真 lftpd FTP 上传,判据 = FTP 协议结果 + **上传后 RETR 回读 SHA-256**)**227 轮全过**(8KB/64KB/256KB/1MB,含零间隔快速重连与 100 轮浸泡),并发生产 UI 流 600 帧 CRC 全过,设备侧无 FAIL/ERROR/WARN。原台架"停摆"系**测试程序自吞数据**(Phase 9/11 的 64B 读、固件探针读走不回显;短收量恰等于被吞字节数)+ **判读工具误判**(`probe_classify.py` A/C 把通过运行报成 STALL,C0 脚本层实测 1460/1460 全匹配)——详见 `CH395F_Trap_Records.md` Trap 21"教训"节。**原残留决策项(数据通道预算缩短)随之关闭** | 2026-08-26 发现 / 2026-09-23 结案 | 结案依据与台架纪律见 Trap 21;真实路径工具 `MDK-ARM/ftp_freeze_probe.py`(上传+回读哈希)、判读 `MDK-ARM/bench_verdict.py`;同类真实缺陷见 Trap 23(已修复) | `CH395F_Trap_Records.md` Trap 21;`CH395F_Test_Guide.md` TC-NET-604 |
| N-2 | **49s 全系统日志静默(defaultTask/NAND syslog 排空停滞)**——串口日志 49s 无行(testTask 10s 周期行/netTask 事件行/[STACK] 行全缺),期间 net 栈照常处理外部连接(FTP banner 92ms)→ 日志管线(defaultTask 排空 → NAND+UART)停滞,队列满后各任务日志行被丢;系统功能不受损 | 2026-09-22 17:23:52~17:24:41(L3 台架 F2 运行中,gen 181) | **A/B 归因:L3 不触碰 defaultTask/syslog 路径 → 既有存储侧现象**。待办:① 复现条件定位(NAND 写/GC 长尾 vs UART TX 卡滞——对照 `CH395F` 无关的 SD2506 侧日志)② 日志队列满的丢弃计数/告警(现静默丢行,排障不可见)③ 与 S8(非阻塞日志)合并评估 | `错误码传播审计.md` E-02 阶段二"台架新发现②";`链路延迟测试说明.md` §6.7 注 |
| N-3 | **L5 队列往返基线(1223/988µs)的 L3 后重验**——L5 测 DONTWAIT 队列全程;L3 对该路径逐位不变、pending tick 无 pending 时为 µs 级指针读 → 机制上无影响,但基线数值属分片时代,严格重锁需跑一轮 Phase 13(需 PC peer) | 2026-09-22(L3 收尾留档) | 低风险:10ms/100ms 阈值余量 >8×;下次 Phase 13 窗口顺带重锁即可 | `链路延迟测试说明.md` §6.4 L5 注¹ |
| N-6 | **Trap 22:快速重连致 CONNECT 中断丢失 → 单连接监听静默死亡(P1,修复待批准)**——轻负载 + 重监听历史 + 断连后 <~1s 重连:芯片握手正常但 MCU 无 standalone accept(状态滞留 LISTENING)→ `net_send` 全失败(-1)→ 断开时 DISCONNECT 落 LISTENING 分支仅告警 → 芯片自动关闭 Socket → 端口永久拒连(不自恢复) | 2026-09-22(Trap 21 二次台架 A 组 run2;出现 1/1,未做 10/10 规模复验) | 修复方案:`handle_disconnect_event` LISTENING 分支回读芯片 socket 状态,非可监听即 `net_relisten_try` 重建(D20 的 LISTENING 分支姊妹情形)+ 可选:重监听后清残留中断位;应用侧规避:单连接 socket 断连后重连 ≥1~2s 退避(lftpd 快速 PASV / 客户端快速重试是生产风险场景)。生产影响:数据通道监听静默死亡 → 后续数据连接全拒,严重度等同端口失效 | `CH395F_Trap_Records.md` Trap 22 |
| N-6 | **✅ 已修复(2026-09-23 当日闭环):Trap 22 快速复连丢 CONNECT/DISCONNECT 中断 → 监听静默死亡**——生产固件确定性复现(待机后 UI `:7003` 快速复连:修复前 2/10 后永久拒连;FTP `:21` 100ms 间隔:第 29 轮起死亡);机制 = 芯片丢 CONNECT 或 DISCONNECT 中断 → 那条连接一直占住单连接监听(后续 SYN 全拒),且丢事件后本层收不到任何中断、纯事件驱动无法自愈。修复 = `net_socket.c` 新增**周期对账 `net_reconcile_tick()`**(200ms,回读芯片状态修正三类失同步:本层 ESTABLISHED 而芯片已断 → 断开;本层 LISTENING 而芯片已握手 → 补 accept;本层 LISTENING 而芯片监听消失 → 重建)+ 去抖(连续 2 次)+ 冷却(1s);`handle_disconnect_event` LISTENING 分支改为"已不可监听即重建"。验证:待机 `:7003` 10/10、`:21` 100ms/0ms/600ms 各 50/50、UI 20/20、FTP 正常 10/10(哈希)、中止 RST/FIN 5/5+5/5、零重建风暴 | 2026-09-22 发现 / 2026-09-23 复现+修复闭环 | 踩坑留档:自愈若放 accept 路径且用单次状态读,会因"刚 listen 未生效"瞬态反复重建直至 `relisten give up`(三监听口全死)——**单次状态读的重建动作必须去抖**。工具 `MDK-ARM/tcp_reconnect_probe.py` | `CH395F_Trap_Records.md` Trap 22;`Drivers/BSP/NET/net_socket.c`(`net_reconcile_tick` / `handle_disconnect_event`) |
| N-8 | **✅ 已修复(2026-09-23 当日闭环):Trap 23 上传中止后首笔复连上传被静默截断(≤2048B)并误报成功**——修复前可信复现 15/15(RST 10/10 + 优雅 FIN 5/5);修复后 RST **10/10**、FIN **5/5** 全通过,正常路径回归 30/30 + 20/20(SHA-256 全匹配)。**真因**:中止时对端仍有在途数据 → 芯片 TIMEOUT 后才把在途段落进 RX 缓冲(约 2048B 残渣)→ 立即复监听时残渣被下一条连接当作自己的数据读走(新上传只"收"2048B 就判结束并回 226)。**修复**:异常释放(TIMEOUT)的 socket 复监听前隔离(持续排空 RX 残渣 + 主动 close + 等 CLOSED/TCP_CLOSED),400ms 起、800ms 上限强制放行(上限须 < 重试放弃时限 2000ms,否则监听被放弃,已实测修正);`net_drain_chip_rx()` 分块读空、零新增静态缓冲;仅异常路径生效,正常 FIN 路径零改动。详见 Trap 23"修复实施与验证"——生产路径 FTP STOR 中途断开(RST 或 FIN 均可)后,紧跟的第一笔复连上传被截断到 ≤2048B(芯片单 socket RX 缓冲)且回报 226 成功 → 客户端认为上传成功 = 静默数据损坏;第二笔起恢复正常 | 2026-09-23(可信复现 + 当日修复闭环;`MDK-ARM/ftp_abort_probe.py` 自动判定) | 已实施:`net_socket.c` 新增 `s_relisten_quarantine[]`(`handle_timeout_event` 置位)+ `net_drain_chip_rx()` + `net_relisten_locked` 隔离门(`NET_RELISTEN_QUARANTINE_MS`=400 / `NET_RELISTEN_SETTLE_MS`=800 < `NET_RELISTEN_DEADLINE_MS`=2000) | `CH395F_Trap_Records.md` Trap 23;`Drivers/BSP/NET/net_socket.c`(`net_relisten_locked` / `handle_timeout_event` / `net_drain_chip_rx`) |
## 存储 / 日志管线
+40 -15
View File
@@ -560,28 +560,53 @@ NET 层 TCP Client(阶段 7)`net_connect()` 返回 0,但 `net_poll()` 报
- 真实缺陷见 **Trap 23**(上传中止后首笔复连上传被静默截断,已可信复现并修复);
- 发现:2026-08-26(阶段 8);结案:2026-09-23。
## Trap 22:重监听后快速重连丢失 CONNECT 中断 → 监听静默死亡
## Trap 22:快速复连丢 CONNECT/DISCONNECT 中断 → 监听静默死亡(2026-09-23 复现并修复)
### 现象
轻负载构建(Phase 6 only)+ 带历史的重监听 socket:上一条连接关闭后约 600ms 到达的第二条连接,芯片侧硬件完成 TCP 握手(PC 连接成功、数据可流入),但 MCU 侧**无 `standalone accept`**(socket 控制块滞留 LISTENING)→ `net_send` 全部被状态机拒绝(`echo: send -1 != recv N`),PC 侧回显 0 字节;接收路径仍可读出数据(RECV_OK 正常、缓冲可排空)。断开时 DISCONNECT 中断命中 `handle_disconnect_event` 的 LISTENING 分支 → 仅记 `WARNING: listen sockN got disconnect` 而无任何处置 → 芯片在 DISCONNECT 后**自动关闭 Socket**(FUN_PARA 默认行为)→ MCU 未再重开 → **监听静默死亡**:此后所有 SYN 被拒(connection refused),不自恢复(仅复位/网络重建可恢复)。
复现:Phase 6-only 构建 + `test/ch395f_socket_test.py tcp_client` 无间隔连续运行 → run1 全过、run2 失同步、run3 起全部 connection refused(2026-09-22 台架 A 组;出现 1/1,未做 10/10 规模复验)。
### 现象(生产固件实测)
单连接监听 socket 在快速复连后**永久失效**(后续 SYN 被拒或超时,10s 内不自恢复),而芯片/网栈其余部分完全正常(其他监听端口照常工作)。两种表现:
- **拒连(ConnectionRefused)**:芯片完成握手但 **CONNECT 中断未上报** → 控制块滞留 LISTENING;或本层因 **DISCONNECT 中断丢失**仍以为有客户端 → 两种情况都会让芯片侧那条连接**一直占住单连接监听**,后续 SYN 全被拒;且丢事件后本层收不到任何中断,无法靠事件自愈。
- 应用侧连锁:`net_send` 被拒(send -1);或应用读不到 EOF(其 `g_client` 永不释放,因而**永不调用 accept**)。
### 机制(实证边界;芯片侧微观机理未解)
1. 芯片硬件完成握手但 CONNECT 中断未上报(或 MCU 侧丢失)→ `handle_connect_event` 未执行 → 状态滞留 LISTENING。
2. **实证边界(2×2 台架)**:失同步仅现于"轻负载 + 重监听历史 + 快速重连(~600ms)"组合;重负载(Phase 6/9/11)下断连后 ~500ms 重连仍同步正常(B' 组 20/20 standalone accept、10/10 全连接成功);轻负载 + 15s 重连间隔亦全正常(A' 组 20/20)。即中断丢失窗口 ≈ "close → 重监听 → SYN" 间隔 < ~1s 且 netTask 相对空闲。芯片侧丢中断的具体原因未解(列为后续观察项,不阻塞下述修复)。
3. 后果链确定:状态滞留 LISTENING → `net_send` 被拒(send -1)→ 断开时 DISCONNECT 落 LISTENING 分支(现有代码仅告警)→ 芯片自动关闭 Socket → MCU 不再重开 → 监听死亡。
4. 与 Trap 21 的关系:Trap 21(TC-NET-604 "RX 窗口冻结")**已结案为非缺陷**(真实生产路径 227 轮全过;原台架"停摆"系测试程序自吞数据 + 判读工具误判,见 Trap 21);本条(Trap 22)是**独立且仍待修**的失效模式:连接状态失同步(缺 standalone accept + `send -1` + `WARNING: listen`),须与之分开跟踪。
### 复现(可信用例,纯客户端判据;工具 `MDK-ARM/tcp_reconnect_probe.py`)
- **确定性复现**:产品固件 + 待机 60s 后,对 UI 监听 `:7003` 以 300ms 间隔快速复连 → 修复前**首 2 次成功、之后永久拒连**(10 轮 2/10,10s 恢复窗口内无自恢复);
- **概率复现**:FTP 控制 `:21` 以 100ms 间隔连 50 次 → 修复前**第 29 轮起永久拒连**;
- 对照:FTP `:21` 在 0ms / 600ms 间隔下通过;`:8888`(对时)同期亦正常 → 触发与"复连间隔 <~1s + 轻负载(待机)"相关,与 2026-09-22 记录的边界一致。
### 修复(方案,未实施 → N-6)
1. `handle_disconnect_event` LISTENING 分支:回读芯片 socket 状态,非可监听状态(即芯片已自动关闭)时执行 `net_relisten_try` 重建(重 OPEN + TCP_LISTEN)——D20 陈旧事件修复的 LISTENING 分支姊妹情形(D20 只处理 ESTABLISHED 分支)。
2. 可选加固:重监听完成后清除该 socket 残留中断位(`handle_connect_event` 已有同模式),防陈旧位致下一次事件误判。
3. 应用侧规避(可立即执行):单连接 socket 断连后重连留 ≥1~2s 退避——客户端快速重试 / lftpd 快速 PASV 序列是生产主要风险场景。
### 机制(设备侧日志实证)
1. 芯片丢 **CONNECT** 中断 → 无 `standalone accept`,控制块滞留 LISTENING;或丢 **DISCONNECT** 中断 → 本层仍以为 ESTABLISHED;
2. 此后本层**收不到任何中断**(芯片静默持有/丢弃该连接),纯事件驱动无路可走——实测把 `handle_disconnect_event` 的 LISTENING 分支从"仅告警"改为"重建监听"后**仍然无效**(因为连 DISCONNECT 都没来);
3. 故必须**周期回读芯片状态对账**才能发现失同步。
### 修复(已实施,`Drivers/BSP/NET/net_socket.c`)
1. **周期对账 `net_reconcile_tick()`**(`net_process_messages` 每轮调用;周期 200ms):对在用 socket 回读芯片状态,修正三类失同步——
① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(应用读到 EOF 后自动重监听);
② 本层 LISTENING 而芯片已完成握手 → 补做 standalone accept(复用 `handle_connect_event`);
③ 本层 LISTENING 而芯片监听已消失 → 重建监听(`net_relisten_try`)。
**去抖 + 冷却**:同一失配需连续 2 次(≈400ms)才动手;同一 socket 动手后冷却 1s。
2. `handle_disconnect_event` 的 LISTENING 分支:由"仅告警"改为"回读芯片状态——已不可监听即重建"(仍在监听则判陈旧事件丢弃)。
### 踩坑记录(务必先读)
最初的修法放在 **accept 路径**(每次 accept 时回读状态并自愈):单次状态读会把"刚 open/listen 尚未生效"的瞬态误判为失配 → 监听被反复重建直至 `relisten give up` → **三个监听口全部死亡(风暴)**。改为"周期对账 + 去抖 + 冷却"后消除。
**结论:凡基于单次状态读的重建动作,必须去抖。**
### 验证(修复前 → 修复后)
| 用例 | 修复前 | 修复后 |
|---|---|---|
| 待机后 UI `:7003` 快速复连 ×10 | 2/10(之后永久拒连) | **10/10** |
| FTP `:21` 间隔 100ms ×50 | 28/50(第 29 轮起死亡) | **50/50** |
| FTP `:21` 间隔 0ms / 600ms ×50 | 通过 | **50/50 / 50/50** |
| UI `:7003` 间隔 200ms ×20 | — | **20/20** |
| FTP 正常上传 10×64KB(SHA-256 往返) | 通过 | **10/10** |
| 上传中止 RST / FIN 回归(Trap 23) | 通过 | **5/5 + 5/5** |
| 设备侧 `relisten give up` / 重建风暴 | — | **0** |
自愈实测计数(整轮会话):`chip closed -> disconnect` **12** 次、`connection completed w/o CONNECT irq -> accept` **1** 次、`listen lost -> relisten` **1** 次——三类失同步均真实出现并被自愈;`relisten give up` 0 次。
### 对应文件
`Drivers/BSP/NET/net_socket.c`(handle_disconnect_event LISTENING 分支);`test/net_test_task.c`(复现:Phase 6-only 构建 + 快速 tcp_client);`docs/待处理内容.md` N-6(修复待办登记)。
`Drivers/BSP/NET/net_socket.c`(`net_reconcile_tick` / `handle_disconnect_event` LISTENING 分支);`MDK-ARM/tcp_reconnect_probe.py`(可信用例);`docs/待处理内容.md` N-6(已闭环)。
### 发现时间
2026-09-22(Trap 21 二次 2×2 复核台架副产品,A 组 run2)。
2026-09-22(台架偶发 1/1)→ 2026-09-23(生产固件确定性复现 + 当日修复闭环)。
## Trap 23:上传中止后首笔复连上传被静默截断(≤2048B)并误报成功