merge: task/n17-probe 合入 v2-on-ui

任务 n17(1 个提交)经 task-worktree 回收:
- fix(网络): 主动拆会话登记即返——drop 路径不再占用 netTask(登记簿 N-17 收口)
This commit is contained in:
2026-10-25 10:32:25 +08:00
6 changed files with 108 additions and 26 deletions
+25 -7
View File
@@ -758,7 +758,9 @@ void net_process_messages(void) {
break; break;
} }
case NET_MSG_DROP_CONN: { case NET_MSG_DROP_CONN: {
/* E-02 收口:应用放弃会话的主动拆除(拆连接、保监听) */ /* E-02 收口:应用放弃会话的主动拆除(拆连接、保监听)。监听型
* 登记即返(N-17:拆链排渣交 net_listener_tick,本消息不再占用
* netTask 数百 ms) */
net_sock_t *p_sock = net_get_sock(msg.sockfd); net_sock_t *p_sock = net_get_sock(msg.sockfd);
if (p_sock == NULL) { if (p_sock == NULL) {
/* 描述符无效 → BADF */ /* 描述符无效 → BADF */
@@ -2044,8 +2046,11 @@ static int net_close_locked(net_sock_t *p_sock) {
* (PC 内核自动 ACK keepalive,僵尸判不死),单连接监听被永久 * (PC 内核自动 ACK keepalive,僵尸判不死),单连接监听被永久
* 占住;应用又不得 net_close 监听 fd(毁监听)——本函数补上 * 占住;应用又不得 net_close 监听 fd(毁监听)——本函数补上
* "拆连接不拆监听"的应用侧形态 * "拆连接不拆监听"的应用侧形态
* 2. 拆除=sock_reset QUARANTINE(TRAP-NET-18 异常释放语义:FIN + 排渣 + * 2. 芯片侧拆除(TRAP-NET-18 异常释放语义,N-17 收口改异步):监听型 fd
* 排空事件(quiescent) + 等 byte0=00H,内置二段 CLOSE 补发) * = net_listener_settle(QUARANTINE) + REQ_DROP 登记即返,动作全部交
* net_listener_tick(隔离窗内持续排空/关闭正是该机的既有语义);
* 非监听 client fd = 同步 sock_reset QUARANTINE(600ms 封顶,原形态)
* ——本处理器不得再对监听型做同步等待(netTask 占用窗见 .h 注)
* 3. auto_relisten(监听型 fd):收口 CLOSED 后登记重建请求(动作在 * 3. auto_relisten(监听型 fd):收口 CLOSED 后登记重建请求(动作在
* net_listener_tick,≤1ms 后执行;芯片已 CLOSED 直接 open 必成, * net_listener_tick,≤1ms 后执行;芯片已 CLOSED 直接 open 必成,
* E4 定案;未成则 30ms 节奏重试 + reconciler 形态④兜底); * E4 定案;未成则 30ms 节奏重试 + reconciler 形态④兜底);
@@ -2079,8 +2084,20 @@ static int net_drop_conn_locked(net_sock_t *p_sock) {
/* 挂起 send 同样先结束(以已发字节数通知;未发部分放弃),再拆芯片会话 */ /* 挂起 send 同样先结束(以已发字节数通知;未发部分放弃),再拆芯片会话 */
net_send_pending_cancel((int)(p_sock - s_net_socks), NET_ERR_CONNRESET); net_send_pending_cancel((int)(p_sock - s_net_socks), NET_ERR_CONNRESET);
/* 确定性拆除(异常释放 → QUARANTINE 400ms 底,TRAP-NET-18 语义) */ /* 芯片侧拆除按 fd 形态分流(异常释放 = QUARANTINE 语义,TRAP-NET-18)。
if (net_sock_reset_sync(sockfd, CH395F_RESET_QUARANTINE) != 0) { * 监听型 fd:只登记沉降隔离 + 重建请求,**立即返回**——“排渣/FIN 关闭/
* 排空事件/等 byte0=00H/重建”整套动作由 net_listener_tick 以 30ms 节奏执行
* (与被动超时释放同一台机器;E16 台架实证该 net 层自建序列 = RST 10/10、
* FIN 5/5 的通过版本)。旧形态在本处理器里 net_sock_reset_sync 同步等待,
* netTask 被占用 400ms 底/600ms 顶:串行总线整窗停摆、UI/FTP/对时的 API 回
* 执顺延、NET 与对时位同窗丢 100ms 抽样 → 800ms 停喂声明 stall,距 IWDG
* 最坏超时余量仅 ~0.7s(登记簿 N-17;方向评估与否决论证 → 《网络自愈架构
* 设计》§4.6)。返回语义随之明确:返回 = 已登记,芯片未必已拆完。
* 非监听 client fd:无监听可保,维持同步确定性排渣(600ms 封顶;全仓
* 零生产调用者,形态原样保留防漂移)。 */
if (p_sock->auto_relisten != 0U) {
net_listener_settle(sockfd, NET_LISTENER_SETTLE_QUARANTINE);
} else if (net_sock_reset_sync(sockfd, CH395F_RESET_QUARANTINE) != 0) {
DBG_ERROR("sock%d drop_conn: sock_reset not done", sockfd); DBG_ERROR("sock%d drop_conn: sock_reset not done", sockfd);
/* 超时继续收口本层;芯片侧交 reconciler/pump 对账接管 */ /* 超时继续收口本层;芯片侧交 reconciler/pump 对账接管 */
} }
@@ -2424,8 +2441,9 @@ int net_close(int sockfd) {
* 函数说明:1. 单连接模型下挂起但 TCP 仍活的客户端会永久占住监听(keepalive * 函数说明:1. 单连接模型下挂起但 TCP 仍活的客户端会永久占住监听(keepalive
* 被 PC 内核自动 ACK 判不死,《网络自愈架构设计》§5.2 E13);应用不得 * 被 PC 内核自动 ACK 判不死,《网络自愈架构设计》§5.2 E13);应用不得
* net_close 监听 fd → 本 API 拆连接不拆监听(E-02 收口) * net_close 监听 fd → 本 API 拆连接不拆监听(E-02 收口)
* 2. netTask 侧执行 sock_reset QUARANTINE + 监听即时重建(见 * 2. 监听型 fd:netTask 侧仅登记隔离沉降 + 重建请求即返回(动作在
* net_drop_conn_locked);等待期 netTask 有界占用 ~400-600ms * net_listener_tick,netTask 零占用,登记簿 N-17 收口);非监听 client
* fd 仍同步排渣(600ms 封顶)。见 net_drop_conn_locked
*/ */
int net_drop_conn(int sockfd) { int net_drop_conn(int sockfd) {
/* 消息体(按值拷入队列,netTask 改的是其副本) */ /* 消息体(按值拷入队列,netTask 改的是其副本) */
+11 -4
View File
@@ -396,15 +396,22 @@ int net_close(int sockfd);
/* /*
* 函数功能:主动拆掉当前 TCP 会话,**保留监听**(E-02 收口) * 函数功能:主动拆掉当前 TCP 会话,**保留监听**(E-02 收口)
* 入口参数:sockfd - socket 描述符 * 入口参数:sockfd - socket 描述符
* 返回值:0 成功(含幂等:无活动会话),-1 失败 * 返回值:0 成功(**监听型 fd 的“成功”= 拆除与重建已登记**,芯片未必已拆完,
* 见函数说明 2;含幂等:无活动会话),-1 失败
* 限定条件:非 netTask 上下文(经消息队列委托,同其它 net_* API) * 限定条件:非 netTask 上下文(经消息队列委托,同其它 net_* API)
* 函数说明:1. 适用场景=应用层放弃会话(对时 30s 预算耗尽、协议超时等): * 函数说明:1. 适用场景=应用层放弃会话(对时 30s 预算耗尽、协议超时等):
* 单连接模型禁止对监听 fd net_close(连监听一起毁,AGENTS 约定), * 单连接模型禁止对监听 fd net_close(连监听一起毁,AGENTS 约定),
* 而挂起但 TCP 仍活的客户端会永久占住单连接监听(PC 内核自动 * 而挂起但 TCP 仍活的客户端会永久占住单连接监听(PC 内核自动
* ACK keepalive,芯片僵尸判不死——《网络自愈架构设计》§5.2 E13 定案缺口) * ACK keepalive,芯片僵尸判不死——《网络自愈架构设计》§5.2 E13 定案缺口)
* 2. 实现=sock_reset QUARANTINE(FIN 关闭 + 排渣 + 排空事件 + 等 * 2. 实现(2026-10-25 N-17 起):auto_relisten fd = 登记 QUARANTINE 隔离
* byte0=00H,TRAP-NET-18 异常释放语义);auto_relisten fd 随即重监听 * 沉降 + REQ_DROP 重建请求,**登记即返**——“FIN 关闭 + 排渣 + 排空
* (失败交 D10 重试),非 auto_relisten(client fd)收口 CLOSED * 事件 + 等 byte0=00H”(TRAP-NET-18 异常释放语义)与重建由
* net_listener_tick 按 30ms 节奏异步执行,端口恢复窗 ~0.4-1s 与被动
* 断连同型;**调用方不得按旧语义假设“返回即芯片已 CLOSED”**——返回
* 后 accept/recv 得 WOULDBLOCK 直至重建完成(time_sync 既有轮询天然
* 容忍)。旧同步形态曾使 netTask 占用 400-600ms、五任务心跳同窗
* 缺席声明 stall(《网络自愈架构设计》§4.6 N-17 记录,勿复活)。
* 非 auto_relisten(client fd)无监听可保,仍同步排渣(600ms 封顶)
* 3. 幂等:非 ESTABLISHED/CONNECTING 直接返 0,不动监听 * 3. 幂等:非 ESTABLISHED/CONNECTING 直接返 0,不动监听
*/ */
int net_drop_conn(int sockfd); int net_drop_conn(int sockfd);
+36 -8
View File
@@ -422,17 +422,45 @@ CLOSED → 由形态④ 接手。关键在两条快路径的判据(`net_pendin
必须 `net_select` 超时或 `net_recv_to` 显式预算。 必须 `net_select` 超时或 `net_recv_to` 显式预算。
- 上层 32 轮盲收(`ui_net_sink` / `iec104_serv`)保留现状(E6 裁决,§4.8)。 - 上层 32 轮盲收(`ui_net_sink` / `iec104_serv`)保留现状(E6 裁决,§4.8)。
### 4.6 `net_drop_conn`——应用主动拆会话保监听(E-02 收口) ### 4.6 `net_drop_conn`——应用主动拆会话保监听(E-02 收口;N-17 异步收口 2026-10-25)
- 语义:`net_pending_settle_close` 先结束挂起 recv(回传结果 + 唤醒调用方)→ - 语义(**返回值 = 拆除与重建已登记**,不代表芯片已拆完):`net_pending_settle_close`
`net_sock_reset_sync(QUARANTINE)` → 本层收口 CLOSED(不清 fd)→ `auto_relisten` 即时登记 先结束挂起 recv(回传结果 + 唤醒调用方)→ 监听型(`auto_relisten`)fd:
重建请求(`net_listener_tick` 执行;失败交 D10 + 形态④)。 `net_listener_settle(QUARANTINE)` + `net_listener_request(REQ_DROP)` 登记即返,
芯片拆除(FIN 关闭 + 排渣 + 排空事件 + 等 byte0=00H)与重建由 `net_listener_tick`
按 30ms 节奏执行(与被动超时/断连释放同一台机器),失败交 D10 + 形态④;
非监听 client fd 无监听可保,仍同步 `net_sock_reset_sync`(600ms 封顶)。
端口恢复窗 ~0.4~1s,与被动断连恢复窗(E1 发现 A)同型。
- **为什么监听型不得同步等待(N-17 定位,论证随结论一并留档)**:旧形态在
`NET_MSG_DROP_CONN` 处理器内同步跑 `net_sock_reset_sync(QUARANTINE)`(2ms 节奏、
400ms 底/600ms 顶,自家注释原记「等待期 netTask 有界占用 ~400-600ms」),netTask
的 1ms 主循环冻结在同一迭代内:NET 位心跳停摆 + 串行总线整窗停摆(UI 推流/FTP/
104 在途消息回执顺延、调用方在其分片/无限等待中不见回执)+ 对时调用方自己睡在
`net_wait_result` 等回执、期间无分片心跳——五位同窗缺席 → 连续 8 个 100ms 抽样
停喂 → 声明 `[WD] task stall`(V5 时代 req=0x37,缺席位恰为 NET/FTP/UI/对时,
快照只剩 bit0,即日志中的 `mask=0x00000001`),缺口 800ms~1s,距 IWDG 最坏超时
1701ms 余量仅 ~0.7s;同场隔离负载(纯 FTP 120s)0 次——因为被动拆链走的本就是
异步沉降机(D10「重建必须非阻塞」条令),只有主动 drop 一条路径留在同步原语里。
- **评估否决的方向——「缩短重建重试(5×2s)」**:该退避链在
`time_sync_open_listener()` 的 bind/listen 失败路径,全程走
`time_sync_delay_fed()` ≤50ms 分片喂心跳,且**不在 drop→拆链→重监听的心跳缺口
链上**;缩短它不消除本缺口,反而加大对芯片沉降窗的撞车风险(TRAP-NET-09:关闭
后需沉降才能重开),故不采。
- **竞态与信源核对**:① 主路径置 CLOSED 后迟到的 DISCONNECT/TIMEOUT 事件被
`net_events` 的状态门拦截(只认活会话态),不会把刚登记的 QUARANTINE 降级为
GRACEFUL;② 零新增读清点——隔离窗内两处本 Socket 读清仍在 `net_listener_tick`
原位置(§4.10.2),全局寄存器唯一读者仍是 `net_poll`;③ 生产调用者全仓仅
`time_sync.c`(不消费返回值,accept 轮询天然容忍 WOULDBLOCK 窗)。
- **幂等**(非 ESTABLISHED/CONNECTING 直接返 0);**EOF 路径不得调** - **幂等**(非 ESTABLISHED/CONNECTING 直接返 0);**EOF 路径不得调**
(`auto_relisten` 已接管,再调属重复拆)。 (`auto_relisten` 已接管,再调属重复拆)。
- 芯片物理边界(E-02 台架):`close` **无 RST 语义**——对端配合(收到 FIN 后其内核 - 芯片物理边界(E-02 台架,异步收口逐字不变——只移动「等待发生在哪」):
close):`TIME_WAIT` ~150ms → 形态④ relisten(恢复全程 ~500ms);对端内核存活但 `close` **无 RST 语义**——对端配合(收到 FIN 后其内核 close):`TIME_WAIT` ~150ms
不 close:芯片停 `FIN_WAIT_2` 长尾,**监听恢复受限于对端 close**,兜底 = 芯片 → 形态④ relisten(恢复全程 ~500ms);对端内核存活但不 close:芯片停
TIMEOUT/KeepAlive 拆僵尸 30~90s(E13)——不是应用可加速的路径。 `FIN_WAIT_2` 长尾,**监听恢复受限于对端 close**,兜底 = 芯片 TIMEOUT/KeepAlive 拆
僵尸 30~90s(E13)——不是应用可加速的路径。
- 回归锚点:`TC-NETSOCK-240efg / 321 全组 / 460ef`(登记式、netTask 零占用、
client fd 旧形态保留);台架判据 = V5 复跑(e02_probe + FTP ≥30min)零 stall,
见 `docs/测试说明/实机测试实施指南.md` §4。
### 4.7 KeepAlive(服务器侧) ### 4.7 KeepAlive(服务器侧)
+4 -2
View File
@@ -550,8 +550,10 @@ static void tc_240_drop_conn_branches(void)
CHECK(g_peers.pending_settle_calls == 1 && g_peers.send_cancel_calls == 1, CHECK(g_peers.pending_settle_calls == 1 && g_peers.send_cancel_calls == 1,
"TC-NETSOCK-240e both pending tables are ended before dropping the session (with notification)"); "TC-NETSOCK-240e both pending tables are ended before dropping the session (with notification)");
CHECK(g_fxchip.rst_begin_calls == 1, CHECK(g_peers.listener_settle_calls == 1 &&
"TC-NETSOCK-240f teardown uses sock_reset deterministic draining, not close plus blind waiting"); g_peers.last_settle_level == (int)NET_LISTENER_SETTLE_QUARANTINE &&
g_fxchip.rst_begin_calls == 0,
"TC-NETSOCK-240f listener teardown is REGISTERED for the tick machine (QUARANTINE settle + REQ_DROP) - the handler starts no reset machine and never waits (N-17)");
CHECK(g_peers.listener_req_calls == 1 && CHECK(g_peers.listener_req_calls == 1 &&
g_peers.last_req_reason == (int)NET_LISTENER_REQ_DROP, g_peers.last_req_reason == (int)NET_LISTENER_REQ_DROP,
"TC-NETSOCK-240g listener-type fd registers REQ_DROP after the drop (rebuild belongs to the listener layer)"); "TC-NETSOCK-240g listener-type fd registers REQ_DROP after the drop (rebuild belongs to the listener layer)");
+18 -3
View File
@@ -274,7 +274,8 @@ static void tc_320_close_two_paths(void)
reset_all(); reset_all();
} }
/* TC-NETSOCK-321 drop_conn 用 QUARANTINE(异常释放语义),且不动监听簿记 */ /* TC-NETSOCK-321 drop_conn:监听型 = 登记式(settle QUARANTINE + REQ_DROP,
* N-17 起 netTask 零占用);非监听 client fd 保留同步排渣;两者都不动监听簿记 */
static void tc_321_drop_conn_settle(void) static void tc_321_drop_conn_settle(void)
{ {
int fd; int fd;
@@ -287,8 +288,11 @@ static void tc_321_drop_conn_settle(void)
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN; g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
CHECK(net_drop_conn(fd) == 0, "TC-NETSOCK-321a drop_conn succeeds"); CHECK(net_drop_conn(fd) == 0, "TC-NETSOCK-321a drop_conn succeeds");
CHECK(g_fxchip.rst_begin_calls == 1 && g_fx.rst_mode[fd] == 1U, CHECK(g_peers.listener_settle_calls == 1 && g_peers.last_settle_fd == fd &&
"TC-NETSOCK-321b session drop uses QUARANTINE with a 400ms floor (TRAP-NET-18 abnormal release, graded against close)"); g_peers.last_settle_level == (int)NET_LISTENER_SETTLE_QUARANTINE,
"TC-NETSOCK-321b listener fd: abnormal-release isolation REGISTERED for the tick machine (TRAP-NET-18 QUARANTINE semantics survive, executed by net_listener_tick)");
CHECK(g_fxchip.rst_begin_calls == 0 && g_ns_error_count == 0,
"TC-NETSOCK-321b2 N-17: the handler starts no synchronous reset and logs nothing - netTask keeps its ~1ms beat");
CHECK(s_net_socks[fd].lost_conn_seen == 0U, CHECK(s_net_socks[fd].lost_conn_seen == 0U,
"TC-NETSOCK-321c new session start: the missed-event debounce counter is cleared (N-30)"); "TC-NETSOCK-321c new session start: the missed-event debounce counter is cleared (N-30)");
CHECK(s_net_socks[fd].local_port == 2404U && s_net_socks[fd].auto_relisten == 1U && CHECK(s_net_socks[fd].local_port == 2404U && s_net_socks[fd].auto_relisten == 1U &&
@@ -300,6 +304,17 @@ static void tc_321_drop_conn_settle(void)
CHECK(s_net_socks[fd].readable == 0U && s_net_socks[fd].state == NET_SOCK_STATE_CLOSED, CHECK(s_net_socks[fd].readable == 0U && s_net_socks[fd].state == NET_SOCK_STATE_CLOSED,
"TC-NETSOCK-321f session bookkeeping closes out: clear readable and move to CLOSED"); "TC-NETSOCK-321f session bookkeeping closes out: clear readable and move to CLOSED");
reset_all(); reset_all();
/* 非监听 client fd:无监听可保,同步排渣形态原样保留(N-17 只改监听型) */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
CHECK(net_drop_conn(fd) == 0, "TC-NETSOCK-321g client-fd drop still succeeds");
CHECK(g_peers.listener_settle_calls == 0 && g_peers.listener_req_calls == 0,
"TC-NETSOCK-321h client fd registers no settle and no REQ_DROP: nothing to rebuild");
CHECK(g_fxchip.rst_begin_calls == 1 && g_fx.rst_mode[fd] == 1U,
"TC-NETSOCK-321i client fd keeps the synchronous QUARANTINE close-out (bounded 600ms, legacy shape)");
reset_all();
} }
/* TC-NETSOCK-322 close 前必先把挂起结算掉,再动芯片(顺序不可换) */ /* TC-NETSOCK-322 close 前必先把挂起结算掉,再动芯片(顺序不可换) */
+14 -2
View File
@@ -554,16 +554,28 @@ static void tc_460_reset_sync_timeout_bound(void)
"TC-NETSOCK-460d the slot is released even on timeout (a chip state must not jam the local fd table)"); "TC-NETSOCK-460d the slot is released even on timeout (a chip state must not jam the local fd table)");
reset_all(); reset_all();
/* drop_conn 同一条超时线 */ /* N-17:监听型 fd 的 drop 不再走复位等待线——登记即返;即便芯片永不落回
* CLOSED 也与本调用无关(排渣与等待归 net_listener_tick 的隔离机) */
bring_up(); bring_up();
fd = mk_established(NET_SOCK_STREAM); fd = mk_established(NET_SOCK_STREAM);
s_net_socks[fd].auto_relisten = 1U; s_net_socks[fd].auto_relisten = 1U;
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN; g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
g_fx.rst_keep_open[fd] = 1U; g_fx.rst_keep_open[fd] = 1U;
t_before = g_ns_tick; t_before = g_ns_tick;
CHECK(net_drop_conn(fd) == 0 && (g_ns_tick - t_before) <= 20U &&
g_ns_error_count == 0 && g_peers.listener_settle_calls == 1,
"TC-NETSOCK-460e listener drop returns registered and off the beat budget: no 600ms close-out, no [ERR] line, isolation armed (N-17)");
reset_all();
/* 非监听 client fd 保留旧超时线(同步、600ms 封顶、留痕) */
bring_up();
fd = mk_established(NET_SOCK_STREAM);
g_fake.cached_open[fd] = CH395F_SOCKET_OPEN;
g_fx.rst_keep_open[fd] = 1U;
t_before = g_ns_tick;
CHECK(net_drop_conn(fd) == 0 && (g_ns_tick - t_before) <= 700U && CHECK(net_drop_conn(fd) == 0 && (g_ns_tick - t_before) <= 700U &&
g_ns_error_count >= 1, g_ns_error_count >= 1,
"TC-NETSOCK-460e drop_conn takes the same bounded close-out and logs it too"); "TC-NETSOCK-460f client-fd drop keeps the same bounded close-out as net_close and logs it too");
reset_all(); reset_all();
} }