@@ -233,3 +233,108 @@
### 回归集建议
把 `n30_repro` (背靠背 STOR)纳入常备回归集——Trap 25 终版回归只含有间隔的 `ftp_regression` ,背靠背形态不在集内,这是本次 9/10 能潜伏的测试面原因(实验方案对照台账已登记)。验收线建议:`n30_repro --n 50` 通过数与 `:21 @0ms× 50` 、`ftp_regression` 、`ftp_abort_probe` 同时全绿,由台架定具体门槛。
## F-NET-17 残余偶发的真身:风暴态(E8 深查,2026-10-13)
E7b 的"残余 ~3%"不是稳定值:同固件同负载,故障率随设备状态在 **2%(全新启动)~ 54%(残渣累积)~ 12%(仅复位) ** 间摆动(实验方案 E8 矩阵)。"偶发"的真身是**可自持的风暴态**,三个缺陷叠加:
1. **沉降自再生锁存 ** ( M1 的硬币):沉降段 `net_listener.c:174` close 之后,拆链收尾在 ~10-20ms 后(Trap 14 延迟锁存)**重新置位** DISCONNECT; `:176` 的 SINT 读清与 close 同轮发生,读不清这枚迟到的锁存 → 沉降最后一轮"清干净"的假象下,relisten 完成后 11ms 首条连接被杀。30ms 重试粒度 vs 10-20ms 锁存延迟 = 每次重建一次硬币——E7b 的 2% 即此。
2. **泵与救回互抢 ** (风暴燃料):沉降段 `:172` `net_drain_chip_rx` 排空的正是 closed-with-data 救回(net_socket.c:1317)依赖的 RX 数据;救回 accept 成功返回 fd(:1352)但会话随即被杀/数据被排空 → lftpd 短读 → 425。E8 窗口 31 次 accept timeout 与 38 次救回并存为直接证据。
3. **芯片 OPEN 卡死放大 ** (自持结构):被杀会话芯片侧仍 OPEN(0x05),新 SYN 被接进卡死 socket( TCP 槽 0x00)→ accept → 下一枚锁存再杀 → 直到 800ms 强制放行 + close 真正落拆链才出簇。风暴期时间线(0.75s 内 3 轮"救回→杀→救回")见实验方案 E8 节。
**设计修正(覆盖 T5 版 M3/M5 表述) ** :
- **M3 判据**: `chip=0x05/0x00` ( Socket OPEN + TCP 槽 0x00) + 连接年轻(<1s)= 逻辑矛盾态(活连接应 0x04)→ 不吃边沿,交对账①电平去抖二次确认。风暴窗口 100% 命中、健康路径(0x00/0x00 或 0x07)零误伤。
- **M5 语义**: `sock_reset` 必须 **drain-until-quiescent ** ( close 后轮询 SINT 直到 DISCONNECT 锁存出现并读清、连续两轮无新锁存才 open+listen);固定时间窗在锁存延迟抖动下必输硬币。
- **新红线**:RX 有数据 = 消费者资产——沉降/pump 重建前必须让位 accept 救回路径,排空后才准 reset;救回与重建必须单一所有者(`sock_reset` 内联裁决)。
## F-NET-17 补充:风暴态三机制代码级核实与 M3/M5 设计(T6, 2026-10-13)
> 行号 = 提交 9ac9879( M1+M2+M4-lite)后的实测行号。只读核实,未改产品代码。
### T6-① 「close 已 CLOSED 的芯片 Socket 产生 DISCONNECT 锁存」——基石**修正**而非照单确认
**手册口径 ** :§5.35「关闭 Socket 前 CH395 会自动断开 TCP 连接」(Trap 记录定案更正引用)——close 的动作序是"先断链再关 Socket";若 TCP 已 CLOSED,断链环节无事可做。§5.52② bit3=1 只豁免 DISCONNECT/TIMEOUT 场景的自动关闭/清缓冲,**不豁免 CLOSE_SOCKET 命令本身**( close 仍清缓冲——net_socket.c:1311 注释"close+open 清缓冲"同此)。SINT bit4=DISCONNECT( ch395f.h:268)。
**证伪强形式(干净情形) ** : TC-NET-204( Trap 07 定案更正,Trap 记录实测表)模式 C 正是本问题的受控实验——DISCONNECT 已发生、芯片停在 `0x05/0x00` (与风暴签名逐位相同),补 `close` → * * +0 ms 回 CLOSED**、重开重连 5/5 OK;且"15 轮重开均未观察到残留 DISCONNECT(0x10)——close 顺带清掉了锁存"。即:**close 一个已 CLOSED 的 Socket 不产生新锁存,反而顺带清掉已锁存的位**。模式 A(close 一条活连接,主动拆链)5 轮同样无残留锁存。
**与 Trap 14 的张力消解 ** : Trap 14 的残留锁存不是 close 命令制造的,而是**拆链完成事件**——"DISCONNECT 可能在与 CLOSED 状态达成几乎同时、甚至之后才锁存"(Trap 记录 Trap 14 根因节),粘滞跨会话(B3 条目"SINT 粘滞到被读")。锁存的生成者是拆链过程(多为对端 FIN 的被动拆链收尾),不是 close 命令。
**风暴窗口的直接反证 ** : E8 时间线里 kill 发生在 `auto-relisten ok` **之前 ** (窗口内 relisten 尚未完成,沉降段 close 还没跑或没再跑)——那几枚 11ms 杀连接锁存**不可能由沉降段 close 产生**。真实来源两条:(a) 上一条 STOR 的客户端 FIN(FTP 正常行为,发完即关,Trap 25 定案)的被动拆链收尾迟到完成(Trap 14/23 的 10~20ms, Trap 25 的 >2s 不可观测窗);(b) 卡死 socket( 0x05/0x00)把新 SYN 接进来又自行拆掉(§5.29 语义域),每轮自产一对 CONNECT/DISCONNECT。
**但"沉降末轮 SINT 读清不能保证静默"的实操结论仍成立 ** ,经两条路径:
1. 拆链完成滞后于末轮读清(lead 说的硬币——生成者是拆链,不是 close);
2. **搁浅 ** :剩余两处全局读清(net_socket.c:1670 `net_connect_locked` 、:2334 `net_close_locked` 等待环)焊死 net_poll 全局门(:441→:464)→ 锁存被搁到下一条连接的 CONNECT 全局位被读到那一刻,net_poll 同轮读 SINT 见 CONNECT|DISCONNECT 双置 → 先 CONNECT( :480-483)后 DISCONNECT( :486-489)→ accept 后毫秒级被杀。这是"锁存跨越整个沉降窗落到下条连接"的投递机制(F-NET-7 残余的实测代价)。
**基石判定 ** :现象成立、机制表述须修正——"close 已 CLOSED Socket 会再锁存"应改写为"**TCP 槽 0x00 不证明拆链已完成;拆链完成事件滞后且粘滞;close 命令本身对死 Socket 不产生锁存(TC-NET-204 模式 C),对活连接的主动拆链在该实验窗口内亦无残留(模式 A)**"。此修正直接改写 M5:静默判据必须是"**等到预期锁存出现并读清 + 连续静默轮**",而不是"close 后假设会生成锁存然后清掉";且静默可被新连接自身流量打破(无界窗)→ **M3 是结构性必需,不是可选加固 ** 。
### T6-② 泵与救回抢同一 RX 缓冲——**证实**,排空后救回判据必然失败(对已存在数据)
- 同一缓冲:`net_drain_chip_rx` ( net_listener.c:102-121)经 GET_RECV_LEN+READ_RECV_BUF 消费(读即推进);closed-with-data 救回判据 net_socket.c:1317 `ch395f_get_recv_len(sockfd) > 0` 读同一寄存器源。两者都在 netTask 上下文(accept RPC 与 pump/relisten 串行)→ **不是数据竞态,是先后竞态 ** :谁先跑谁定生死。
- 排空后 `recv_len==0` → :1317 判据对"排空时已存在的数据"**必然失败** → accept CLOSED 分支落到 :1327 即时 relisten(同样进沉降段)→ 资产二次销毁(close 清缓冲)。唯一例外:引擎仍活、客户端续发,可在两次排空间隙回填 RX——但沉降段每 30ms 排一次(:172 每重试轮)、lftpd 100ms 轮询一次 → 泵压倒性赢。
- E8 佐证:风暴窗 31 次 accept timeout 与 38 次救回并存 = 竞态双峰的直接证据(赢一次的救回拿到的也常是被排空半份的数据 → 短读 425)。
- 结论:lead 的新红线成立。裁决点收进 sock_reset 第一步(见 T6-④ QUIESCE 第 1 步):RX>0 且芯片 st[0]==OPEN 且本层 CLOSED+auto_relisten → **让位 accept ** (返 BUSY 不排空),RX==0 或强制放行后才准排空+reset。
### T6-③ M3 最小补丁设计(一行级判据 + 一行级前置清零)
**字段选型:复用 `lost_conn_seen`( net_types.h:190)作 ESTABLISHED 期"可疑电平"( 0/1),零 RAM ** 。
- 生命周期兼容:探针只在 LISTENING 期跑(accept 门 :1298),与 ESTABLISHED 期不重叠;relisten( net_listener.c:249) /pump③(:538) /listen( :1121) /RESYNC( :792)已有清零点。
- **前置一行(必须)**: `handle_connect_event` 现**不清** lost_conn_seen( grep 实测:清零点无此函数)——LISTENING 期探针残留的计数会带进新连接,使可疑判据首轮即被旁路、M3 失效。须在两个转 ESTABLISHED 分支(:3306/:3344 附近)补 `p_sock->lost_conn_seen = 0U;` 。
- 备选(新增 `uint32_t estab_ms` 支持"<1s 年轻窗", 32B/8 sock)不推荐:矛盾判据本身即判别器,flag 比时钟更准——滞后锁存同样能杀老年连接,age 窗会漏;代价仅为真 RST 释放也延 ~400ms(可接受,见误伤分析)。
**判据插入点 ** : `handle_disconnect_event` ESTABLISHED 分支,D20 甄别(:3410-3415)之后、释放留痕(:3418)之前——`st[]` 已在 :3408 读过,**零额外 SPI**:
``` c
if ( ( st [ 0 ] = = CH395F_SOCKET_OPEN ) & & ( st [ 1 ] = = CH395F_TCP_CLOSED ) & &
( p_sock - > lost_conn_seen = = 0U ) ) {
p_sock - > lost_conn_seen = 1U ;
DBG_WARN ( " sock%d suspect DISCONNECT on live conn (chip 0x05/0x00), defer to reconcile " , sockfd ) ;
return ;
}
```
**交对账①的衔接语义——现有代码可直接认领,对账零改动 ** :对账①判据(:588-589) `ESTABLISHED && (st[0]==CLOSED || st[1]==CLOSED)` , 0x05/0x00 经 `st[1]==CLOSED` 命中;去抖 `s_bad` 连续 2 轮(200ms× 2≈400ms)后复用 `handle_disconnect_event` ( :633)再入——此时 `lost_conn_seen==1` 旁路可疑判据 → 正常释放(GRACEFUL 登记、session_dead、CLOSED、回调全套不变)。双锁存场景(第二枚边沿到来)同样因 flag 已置而直接释放:双锁存=强证据,不再三确认。
**去抖期间的状态摆位——保持 ESTABLISHED、session_dead 保持 0,这正是收益所在 ** :
1. lftpd accept 轮询见 ESTABLISHED → :1305 门通过 → **accept 正常返 fd ** (不再依赖 closed-with-data 救回);
2. recv 快路径无状态门槛 → 消费者在 400ms 租约内排空 RX(引擎活则数据续到);
3. 对 n30 的小段 STOR:400ms 租约足以让传输完成 + 客户端正常 FIN(0x07 健康释放)→ **风暴链被"会话正常完成"掐断 ** ;
4. 槽恢复 0x04 的形态(Trap 20 时代可恢复类):对账① bad=0 → 连接存活,flag 由 relisten/connect 清;
5. 引擎真死:消费者排空后 WOULDBLOCK,对账①释放后得 EOF——EOF 晚 ~400ms,语义不变。
* * :21 @0ms 快复连为何零误伤**:
1. 健康 FIN 释放:芯片处 CLOSE_WAIT(0x07)( bit3=1 芯片不关 Socket, CLOSE_WAIT 等 MCU 关——D20 注释 :3403),判据要求恰 `0x05/0x00` , 0x07 不命中;
2. 芯片静默回收形态 `0x00/0x00` :判据要求 st[0]==0x05,不命中;
3. 真 RST(恰 0x05/0x00)会命中可疑 → 代价仅释放延 ~400ms(对账①两轮),不是错状态;且 @0ms 复连时序已被 pump 窗主导(E7b `:7003` p50=1011ms),400ms 落在窗内不可见;
4. E5 健康 FIN 20/20 样本走 ①②的形态,均在可疑集合之外。
**成本 ** :判据 4 行 + connect 清零 1 行 + 1 条 DBG( ~90B Flash),零 RAM。
### T6-④ M5 `sock_reset` drain-until-quiescent 状态机草案(阶段 2)
替换现三数组 `s_relisten_quarantine/_settle_start/_settle_dead` ( net_listener.c:150-208)为 per-socket 相位机;五个重建入口(pump/D10/accept CLOSED 分支/recv EOF/pending③)已全部收敛到 `net_listener_try` ,状态机天然单所有者。
```
IDLE ──(settle 登记或 try 进入)──▶ CLOSE
CLOSE: 发一次 CLOSE_SOCKET( TC-NET-204:顺带清已锁存位,保留此动作);t0=now; latch_seen=0; quiet=0
QUIESCE(每 30ms 重试轮,非阻塞):
1. RX 裁决(红线): GET_RECV_LEN>0 且芯片 st[0]==OPEN 且本层 CLOSED+auto_relisten
→ 让位 accept:返 BUSY,本轮不排空不 close
否则 → net_drain_chip_rx 排空
2. GET_INT_STATUS_SN 读即清: 见 DISCONNECT 位 → latch_seen=1, quiet=0;
否则若 latch_seen → quiet++
3. GET_SOCKET_STATUS: 记 st
4. 静默成立 := st[0]==CLOSED
&& (latch_seen || now-t0 >= LATCH_GRACE_MS) // 等过锁存延迟窗
&& quiet >= QUIET_ROUNDS // 连续两轮无新锁存
&& RX==0
&& now-t0 >= hold_min // GRACEFUL 100 / QUARANTINE 400(沿用)
→ OPEN
5. now-t0 >= RESET_MAX_MS → 强制放行 OPEN(留痕,沿用现兜底)
OPEN: SET_PROTO_TYPE/SET_SOUR_PORT/OPEN/TCP_LISTEN + 成功尾(现 :207-267 不动);
失败 → D10 30ms 重试,每轮先回 QUIESCE 第 2/3 步轻量复核(1 条 SINT + 1 条状态)
```
- 参数建议:`LATCH_GRACE_MS=50` (实测锁存延迟 10~20ms,×2.5 余量)、`QUIET_ROUNDS=2` ( 2× 30ms)、`RESET_MAX_MS=800` (保持"必须 < 2000 放弃时限"规则)、`hold_min` 沿用两级。
- **诚实边界(来自 T6-① 修正)**:静默不是有界保证——拆链完成可被**新连接自身流量**触发(Trap 25 窗口 >2s 不可观测),状态机只能静默"已在途的拆链";"未来流量触发的拆链"只有 M3 的电平确认能接住。**M5 与 M3 互补成对,不是替代关系**:M5 消灭"沉降窗内可静默"的绝大多数锁存(E7b 的 2% 硬币),M3 兜住无界残余(风暴态)。
- RAM:三数组(uint8 + 2× uint32 ≈ 9B/sock)→ 相位 uint8 + t0 uint32 + 打包 flags ≈ 6B/sock,微负,预算友好。
- 与 T6-② 的闭环:QUIESCE 第 1 步即红线裁决点——"泵与救回互抢"在状态机内单点裁决,五入口不再各自竞速。