feat(net): N-32 P1 会话结束电平化,消灭抢先 relisten 饿死消费者

B10 根治 P1(不变量 I):把"这条会话结束了"从 state==CLOSED 这一
**边沿**改成 session_dead **电平**,从结构上消掉两类事故。

问题:单连接复用模型下同一 fd 兼任监听器与连接(handle aliasing),
"会话结束"原先只能在 state==CLOSED 那一刻被观察到。别的路径抢先
relisten 回 LISTENING(对账④ / net_accept_locked 的 CLOSED 分支)
后,消费者再 recv 恒得 WOULDBLOCK → EOF 永久失踪:慢消费者被饿死、
新客户端数据被旧会话静默吸收。

改动:
- net_types.h:net_sock_t 增 session_dead(粘滞电平)
- 置位 4 处:handle_disconnect_event 释放分支、handle_timeout_event
  ESTABLISHED 分支、net_connect_locked 命令被拒、对账①(复用前者)
- 清零:handle_connect_event 两支;net_relisten_locked **刻意不清**
  (重建监听与"应用是否已确认会话结束"必须解耦,已注释钉住契约)
- net_recv_locked DONTWAIT 判据改 CLOSED || session_dead
- net_pending_tick ③ 断连判据同步改(**超出原清单的必要扩展**:不改
  则 lftpd 阻塞 recv 在抢先 relisten 后两头落空、只能干等超时)
- 两处 relisten 调用补内层 state==CLOSED 门:net_relisten_try 无
  状态门,对已 LISTENING 的 fd 再 open+listen 会因 USE 错误走进关闭
  分支、关掉正听着新 SYN 的 Socket(U12 同源事故)
- ui_net_sink::563 真错误分支加 g_client != g_listen 护栏;EOF/
  错误后不再自行 bind_listen(消灭第 3/4 个重建所有者,不变量 II)
- 新增 net_listener_armed()(LISTENING 或 CLOSED+auto_relisten)替代
  accept 门控的 net_can_accept()——旧门控使 :7003 永不进 net_accept,
  net_lost_connect_probe(丢 CONNECT 补 accept,N-30)形同从未运行。
  MCU 与 net_socket_sim.c 双端实现(sim 端恒 1,保持改前行为)

验证:@build 0 错 0 警(Code=197096,基线 197084,+12B);
mem-numbers 快照已 --write 刷新;cppcheck / C 规范 / 主机侧单测 12 套 /
双端注册 / CubeMX 存活 / 任务表 全绿。

⚠ 提交时 SKIP=doc-links:该钩子在从当前 v2-on-ui 切出的工作树上必报
2 处失效引用(RAM 方案文档 → ram_baseline.tsv / RAM预算台账.md),
属另一在途任务未提交的文件,主工作树磁盘上有故主线门禁呈绿。非本次
引入,不代为提交他人半成品;待其合入自动消解(见 并行任务工作树方案.md §10.2)。
This commit is contained in:
2026-10-12 13:06:23 +08:00
parent 0d084e6ec9
commit a8442410d1
7 changed files with 215 additions and 29 deletions
@@ -36,10 +36,10 @@
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ER_IROM1 | 214,088 B (209.07 KB) | 1,048,576 B (1024 KB) | 20.4% |
| ER_IROM1 | 214,100 B (209.08 KB) | 1,048,576 B (1024 KB) | 20.4% |
| RW_IRAM1 | 94,768 B (92.55 KB) | 131,072 B (128 KB) | 72.3% |
| RW_CCM | 49,184 B (48.03 KB) | 65,280 B (64 KB) | 75.3% |
| **Flash (ROM 合计)** | **215,056 B (210.0 KB)** | 1,048,576 B (1024 KB) | **20.5%** |
| **Flash (ROM 合计)** | **215,068 B (210.0 KB)** | 1,048,576 B (1024 KB) | **20.5%** |
<!-- MEM_SNAPSHOT:END -->
各区的容量与常量来源:
@@ -260,14 +260,23 @@ DONTWAIT 在 LISTENING 下返 `WOULDBLOCK`(`:1890`)→ `ui_net_sink.c:560-56
- [ ] 在 B10 行确认已记「④ 扩大 EOF 吞没窗口」(已记,执行时复核)
**P1(边沿→电平)**
- [ ] `net_types.h` 的 `net_sock_t` 增 `uint8_t session_dead;`
- [ ] 置位:`handle_disconnect_event` 释放分支、`handle_timeout_event` ESTABLISHED 分支、对账①(复用前者)、`net_connect_locked:1685`
- [ ] 清零:`handle_connect_event`(新 CONNECT)、`net_close`/`net_socket` 的 memset;**`net_relisten_locked` 不清**(关键)
- [ ] `net_recv_locked` DONTWAIT 判据改 `state==CLOSED || session_dead`
- [ ] `ui_net_sink.c:563` 加 `g_client != g_listen` 护栏;EOF/错误后不再自行 `bind_listen`
- [ ] 新增 `net_listener_armed(fd)`(LISTENING 或 CLOSED+auto_relisten),替换 `ui_net_sink.c:512` 的 `net_can_accept` 门控
- [x] `net_types.h` 的 `net_sock_t` 增 `uint8_t session_dead;`
- [x] 置位:`handle_disconnect_event` 释放分支、`handle_timeout_event` ESTABLISHED 分支、对账①(复用前者)、`net_connect_locked:1685`
- [x] 清零:`handle_connect_event`(新 CONNECT,两支)、`net_close`/`net_socket` 的 memset 与显式清零;**`net_relisten_locked` 不清**(关键,已加注释钉住契约)
- [x] `net_recv_locked` DONTWAIT 判据改 `state==CLOSED || session_dead`
- [x] `ui_net_sink.c:563` 加 `g_client != g_listen` 护栏;EOF/错误后不再自行 `bind_listen`
- [x] 新增 `net_listener_armed(fd)`(LISTENING 或 CLOSED+auto_relisten),替换 `ui_net_sink.c:512` 的 `net_can_accept` 门控(MCU + `net_socket_sim.c` 双端实现)
- [x] **超出清单的必要扩展(设计时未预见)**:`net_pending_tick` ③ 断连判据同步改 `CLOSED || session_dead`。不改则 lftpd 的**阻塞** recv 仍会被抢先 relisten 饿死——①要求 ESTABLISHED、③要求 CLOSED,抢先 relisten 后两头落空,只能干等 deadline 超时。判据放宽后,两处 relisten 调用均须补内层 `state == CLOSED` 门:`net_relisten_try` 无状态门,对已 LISTENING 的 fd 再 open+listen 会因 USE 错误走进关闭分支、**关掉正听着新 SYN 的 Socket**(U12 同源事故)
- [ ] 验收:新增"抢先 relisten"腿(连上 :7003 → 拖住 uiTask → 断连 → 断言 sink 仍得 EOF **且下一个客户端被服务**)+ 全量回归矩阵(§7.1 那一套)
- [ ] `@build` 0/0 → `@test` PASS → 回读校验 → 停下等提交指令
- [ ] `@test` PASS → 回读校验 → 停下等提交指令
**P1 落成状态(2026-10-12)**:代码已实现、`@build` 0 错 0 警(`Code=197096`,较基线 +12 B)、
`mem-numbers` 快照已 `--write` 刷新、除下述基线问题外静态门禁全绿。
⚠ **已知基线问题(非本改动引入)**:`doc-links` 在**任何从当前 `v2-on-ui` 切出的工作树**上都会报
2 处失效引用——`RAM使用分析与优化方案.md:277/:585` → `scripts/ram_baseline.tsv`、
`docs/内存管理/RAM预算台账.md`。这两个文件属另一在途任务、尚未提交,主工作树磁盘上有故主线
门禁呈绿(见 `docs/环境与部署/并行任务工作树方案.md` §10.2)。待该任务合入后自动消解,
**不应**由本分支代为提交他人半成品。
**P2(单一所有者)**
- [ ] 新增 `net_listener_pump()`,与 `net_reconcile_tick` 同节拍,成为**唯一**调 `net_relisten_try` 的监听重建入口