新增Trap23:上传中止后首笔复连上传被静默截断(2048B)并误报成功,可信复现15/15,N-8待修

This commit is contained in:
2026-09-23 01:10:37 +08:00
parent 0e6efb44a3
commit 5a41b966fc
2 changed files with 39 additions and 0 deletions
+1
View File
@@ -14,6 +14,7 @@
| N-3 | **L5 队列往返基线(1223/988µs)的 L3 后重验**——L5 测 DONTWAIT 队列全程;L3 对该路径逐位不变、pending tick 无 pending 时为 µs 级指针读 → 机制上无影响,但基线数值属分片时代,严格重锁需跑一轮 Phase 13(需 PC peer,窗口与 N-1 定位可并案) | 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-7 | **⛔ 已撤回(2026-09-23):前置机理为台架假象(分类器误判 + 固件内读者吞噬对端数据),"读必读空"未被任何有效台架验证 → 本项暂不实施**(原提案:net 层非阻塞 RX 读强制排空至空;撤回依据与本轮证据见 `CH395F_Trap_Records.md` Trap 21 顶部更正块。原"致命类 = 部分窗口重通告(排空后缓冲残留):大 Δ(1KB 块读,C/C0 配方)10/10 确定性停摆;小 Δ(64B)相位刀口(一次性落点 10/10 过 vs 5/5 停摆仅数 ms 时移之差)且持续重复必停摆(50/50);排空至空(窗口→满)恒安全(40/40,含 10ms 节拍 E0)、Δ=0 命令(状态读/中断读/poll/TX)恒安全 → **无需时间守卫/冷却**(旧"排空后 RX 暂缓 + 120ms 冷却"设计作废:C0 证明部分排空自身即致命,E0 证明排空至空后 10ms 再排空安全) | 2026-09-22(精确定位轮:E/O/B/F1/S1–S4/C/C0/V1F/O1f 共 11 组台架 110+ 运行 + netcmd 命令序列日志) | 实施要点(2026-09-22 复核细化,完整方案见 `问题记录/CH395F_Trap21_机制图解.md` §5):net 层新增 `net_drain_recv()` 助手,替换 5 处 `min(货架现有, 调用方len)` 读路径——`net_recv_locked:1205`、`net_pending_tick:1514`/`:1536`、`net_pending_settle_close:1618`、`net_recvfrom:2072`:先交付中转筐 → 筐空则 `avail = get_recv_len()` 后**按 avail 全量读空**入筐 → 只交 `min(avail, len)` 给调用方,余量留筐、下次不发芯片命令直接交付(上层筐大小与解析逻辑零改动);中转槽设计(2026-09-22 复核:**一个共用槽即可**,8KB 方案作废):数据通道自带筐 1KB→2KB(`lftpd.c:187`,`len=2048 ⇒ min(avail,len)==avail`,天然读必读空、永不入槽、不参与争用),控制行 512B / UI 64B / 时间同步 16B 共用**一个 2048B 槽**;槽被别的 socket 占用时该读暂缓(WOULDBLOCK / pending 继续等),不发命令即无残余 → 合计 **+3KB**(主 SRAM 余量 47.2KB,占 6.3%),槽数 1→2 仅一行改动;争用暂缓记限频日志。**只改 lftpd 数据筐不足以覆盖**:控制行读、UI 64B、时间同步 16B 为同型雷(且单纯加大控制行缓冲会丢流水线命令;零槽方案需 +6.5KB 并重构控制行读取器)。台架回归:C/C0 与 V1 配方由 10/10 停摆 → 10/10 通过,E 系列保持 40/40;Δ=0 命令(状态读/poll/TX)不受限 | `CH395F_Trap_Records.md` Trap 21;`CH395F_Trap21_机制图解.md`;`Drivers/BSP/NET/net_socket.c`(5 处读路径);`Drivers/BSP/NET/lftpd/lftpd.c` receive_file 块循环 |
| N-8 | **Trap 23:上传中止后首笔复连上传被静默截断(≤2048B)并误报成功(P1,已可信复现 15/15,修复待批准)**——生产路径 FTP STOR 中途断开(RST 或 FIN 均可)后,紧跟的第一笔复连上传被截断到 ≤2048B(芯片单 socket RX 缓冲)且回报 226 成功 → 客户端认为上传成功 = 静默数据损坏;第二笔起恢复正常。机制:中止后 net 层立即 relisten(端口复用),旧连接收尾的陈旧 DISCONNECT 落在新连接上 → lftpd 读循环误判 EOF,把已到的 2048B 当整文件。对照:无中止时 177 轮(8KB~1MB)SHA-256 全匹配 | 2026-09-23(可信复现首轮;`MDK-ARM/ftp_abort_probe.py` 自动判定,RST 10/10 + 优雅 FIN 5/5) | 修复方向:① relisten 前确认芯片 socket 真 CLOSED 并清中断(现有单次清理不足)② 新连接 accept 后未读任何数据即收 DISCONNECT → 判陈旧中断并重新 relisten(勿当 EOF 结束上传)③ `receive_file` 区分正常 EOF 与异常结束,异常回 5xx 而非 226 ④ 回归:`ftp_abort_probe.py` followup FAIL→OK(10/10),177 轮对照仍全绿 | `CH395F_Trap_Records.md` Trap 23;`Drivers/BSP/NET/lftpd/lftpd.c` receive_file;`Drivers/BSP/NET/net_socket.c` relisten/事件处理 |
## 存储 / 日志管线
+38
View File
@@ -635,3 +635,41 @@ CH395F 片内 TCP 状态机的角(芯片固件,MCU 侧无法修补)。2026
### 发现时间
2026-09-22(Trap 21 二次 2×2 复核台架副产品,A 组 run2)。
## Trap 23:上传中止后首笔复连上传被静默截断(≤2048B)并误报成功
### 现象
客户端上传(STOR)中途断开(用户取消 / 网络断;**RST 或优雅 FIN 均可**),**紧随其后的第一笔复连上传被截断到 ≤2048 字节(= 芯片单 socket RX 缓冲大小),且服务端回报 226 上传成功** → 客户端(ftplib 等)据此认为上传成功 = **静默数据损坏**;第二笔及以后恢复正常。
### 证据(可信复现:生产固件、零固件探针;工具 `MDK-ARM/ftp_abort_probe.py`)
- 判据 = FTP 协议结果 + **紧接一笔正常上传的 SHA-256 往返校验(STOR→RETR 比对)** + 控制通道 NOOP 存活探测;
- **RST 中止:10/10 复现**首笔复连上传截断(请求 4096B → 设备日志 `UPLOAD '/ab_ok.bin' 2048 bytes OK`);
- **优雅 FIN 中止:5/5 同样复现** → 与 RST/FIN 无关,凡"数据未发完即断开"均触发;
- 中止那一笔自身也被记为 `UPLOAD ... 2048 bytes OK` + 226(FTP 无长度声明,语义边界;但客户端会误判成功);
- **对照(无中止)**:177 轮(8KB/64KB/256KB/1MB,含 100 轮零间隔浸泡)全部 SHA-256 匹配(`MDK-ARM/ftp_freeze_probe.py`),并发 UI 流 600 帧 CRC 全过 → 中止路径是唯一触发条件。
### 机制(设备侧日志实证)
1. 中止时数据 socket 被释放(RST 时芯片上报 `sock4 TIMEOUT`),net 层随即 `auto-relisten ok` + `relisten retry ok`(**监听端口复用**);
2. 下一笔连接:`sock4 standalone accept` → `CMD STOR` → **紧接着一条陈旧 `sock4 DISCONNECTED, releasing (chip tcp=0x00)`**(旧连接收尾状态落在新连接上);
3. lftpd 数据读循环据此读到 EOF → 把已到的 2048B 当作整个文件 → `UPLOAD ... 2048 bytes OK` + 226;
4. 再下一笔连接时芯片状态已干净 → 正常(故为"一次性"而非持久故障)。
### 影响
- **静默数据损坏**:上传被中断后"重试的第一笔"会生成截断文件且客户端收到成功回报(FTP 客户端通常不做长度校验)→ 现场可能表现为"上传的文件损坏/不完整";
- 主风险面 = 生产路径 STOR(上传);RETR(下载)不涉及;
- 与 TC-NET-604 的关系:本条为**独立、已复现**的真实缺陷;TC-NET-604 的台架"冻结"结论已撤回(见 Trap 21 顶部更正块),现场"卡死/失败"报告应优先用本条的判据(协议结果 + 哈希往返)复核。
### 复现
```
& '.venv\Scripts\python.exe' MDK-ARM\ftp_abort_probe.py --rounds 10 --send 8192 --gap 1
& '.venv\Scripts\python.exe' MDK-ARM\ftp_abort_probe.py --rounds 5 --send 8192 --gap 1 --graceful
```
### 修复方向(待办 N-8)
1. **relisten 前让芯片 socket 真正沉降**:确认 `ch395f_get_socket_status == CLOSED` 且清掉该 socket 中断状态后再 listen(现有 relisten 的单次清理在中止场景下不足);
2. **新连接的陈旧中断防御**:数据 socket 刚 accept、尚未读到任何数据就收到 DISCONNECT 时判为陈旧中断 → 重新 relisten,而不是当作正常 EOF 结束上传;
3. **应用侧兜底**:`receive_file` 区分"正常 EOF"与"连接异常结束",异常时回 5xx(让客户端重试)而非 226,避免误报成功;
4. **回归判据**:`ftp_abort_probe.py` 的 `followup` 由 FAIL → **OK(10/10)**,且 `ftp_freeze_probe.py` 的 177 轮对照仍全匹配。
### 发现时间
2026-09-23(可信复现首轮:真实生产路径 FTP 中止/复连场景,由 `MDK-ARM/ftp_abort_probe.py` 自动判定)