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:
@@ -959,6 +959,9 @@ int net_socket(int domain, int type) {
|
||||
p_sock->readable = 0;
|
||||
/* 新 fd 不得继承上一条连接的释放原因(Trap 23 待办 #3) */
|
||||
p_sock->released_abnormal = 0;
|
||||
/* N-32(P1):新 fd 不得继承"上一条会话已结束"电平(否则新建即 EOF,
|
||||
* 消费侧连数据都收不到)——与 released_abnormal 同理,显式清零 */
|
||||
p_sock->session_dead = 0;
|
||||
/* 新 fd 不得继承上一次的待重试状态(D10) */
|
||||
net_relisten_clear(sockfd);
|
||||
/* 新 fd 不得继承上一次挂起的 recv 请求(L3) */
|
||||
@@ -1453,6 +1456,49 @@ int net_can_accept(int sockfd) {
|
||||
return (p_sock->state == NET_SOCK_STATE_ESTABLISHED) ? 1 : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:查询监听 Socket 是否处于"应该尝试 accept"的就绪态(纯 RAM 读,不发起 RPC)
|
||||
* 入口参数:sockfd - 监听 Socket 描述符 int
|
||||
* 出口参数:无
|
||||
* 返回值:1 - 就绪(LISTENING,或 CLOSED + auto_relisten = 断连待重建态);
|
||||
* 0 - 未就绪(fd 未分配 / 非监听型 / 正处隔离期以外的不就绪态) int
|
||||
* 限定条件:任意任务上下文;net_init() 已调用
|
||||
* 函数说明:1. N-32(P1 不变量 II):取代 net_can_accept()(要求 ESTABLISHED)作为
|
||||
* ui_net_sink 的 accept 门控。原门控下 :7003 永不进 net_accept →
|
||||
* net_lost_connect_probe(丢 CONNECT 中断补 accept)对该端口从不运行
|
||||
* (设计文档 §2.3 末"探针失效")
|
||||
* 2. 为什么放行 LISTENING:探针与"closed-but-readable 接受"(:1338)
|
||||
* 只在 net_accept_locked 内部发生,门控挡在外面就永远到不了;
|
||||
* LISTENING 态 accept 由探测补 accept 或返 WOULDBLOCK,均安全
|
||||
* 3. 为什么放行 CLOSED + auto_relisten:单连接模型断连后 fd 停在
|
||||
* CLOSED 等 relisten(D10 非阻塞重试 / 对账④),此窗口内新 SYN
|
||||
* 可能已被芯片接住(对账②)→ 放行让 net_accept_locked 的状态门
|
||||
* 自行裁决(含 RX 有数据的 drain_pending 接受)
|
||||
* 4. 只读控制块,不入队、不等通知 → uiTask 不被 netTask 拖住
|
||||
* 5. 与 net_can_accept 语义差异是**有意的**:本函数表达"这个监听器
|
||||
* 还在服役",而非"这一轮一定有连接"
|
||||
*/
|
||||
int net_listener_armed(int sockfd) {
|
||||
/* Socket 控制块(纯 RAM 读,可能为 NULL = 描述符越界或未分配) */
|
||||
const net_sock_t *p_sock = net_get_sock(sockfd);
|
||||
|
||||
if (p_sock == NULL) {
|
||||
return 0;
|
||||
}
|
||||
/* 正常监听态 */
|
||||
if (p_sock->state == NET_SOCK_STATE_LISTENING) {
|
||||
return 1;
|
||||
}
|
||||
/* 断连待重建态:监听器仍在服役(auto_relisten + 已绑端口),
|
||||
* 芯片侧交给 relisten / 对账恢复;同 net_reconcile_tick ④ 的认领口径 */
|
||||
if ((p_sock->state == NET_SOCK_STATE_CLOSED) &&
|
||||
(p_sock->auto_relisten != 0U) &&
|
||||
(p_sock->local_port != 0U)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:查询 Socket 是否"值得发一次 recv"(纯 RAM 读,不发起 RPC)
|
||||
* 入口参数:sockfd - Socket 描述符 int
|
||||
@@ -1682,6 +1728,10 @@ static int net_connect_locked(int sockfd, const struct net_sockaddr *addr, int a
|
||||
if ((status != CH395F_ERR_SUCCESS) && (status != CH395F_ERR_BUSY)) {
|
||||
DBG_ERROR("[CONN] sock%d TCP_CONNECT rejected by chip: 0x%02X", sockfd, status);
|
||||
(void)ch395f_close_socket((uint8_t)sockfd);
|
||||
/* N-32(P1 不变量 I):命令级失败同样是"本会话已结束"——此前这里只置
|
||||
* CLOSED,而 CONNECTING 态从未置 session_dead,挂起的 recv 只能干等
|
||||
* deadline(B2 审计发现的"停在 CONNECTING 干等 30s 超时才暴露"同源) */
|
||||
p_sock->session_dead = 1U;
|
||||
p_sock->state = NET_SOCK_STATE_CLOSED;
|
||||
return NET_ERR_RETURN_1(NET_ERR_INVAL);
|
||||
}
|
||||
@@ -1876,10 +1926,20 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
|
||||
if (flags & NET_MSG_DONTWAIT) {
|
||||
/* 非阻塞且当前无数据:若连接已断开则视为 EOF(0) 并自动重监听,
|
||||
* 否则返回 WOULDBLOCK(-1) 表示暂时无数据 */
|
||||
/* 连接已断开(CLOSED):按 EOF 处理 */
|
||||
if (p_sock->state == NET_SOCK_STATE_CLOSED) {
|
||||
/* 启用自动重监听:先把监听器重新置回 LISTEN */
|
||||
if (p_sock->auto_relisten) {
|
||||
/* 连接已断开(CLOSED)**或**本会话已结束(session_dead):按 EOF 处理。
|
||||
* 判据从纯 `state == CLOSED` 放宽为 `|| session_dead`(N-32 P1 不变量 I):
|
||||
* 抢先 relisten 后 state 已是 LISTENING 而会话确实已结束,旧判据在此
|
||||
* 恒返 WOULDBLOCK → EOF 永久失踪(新连接被旧会话静默吸收)。
|
||||
* 有数据时走上方快路径(recv_len>0 优先),本判据只在"无数据"时生效,
|
||||
* 故不会把新连接的起始数据误判为 EOF。 */
|
||||
if ((p_sock->state == NET_SOCK_STATE_CLOSED) || (p_sock->session_dead != 0U)) {
|
||||
/* 启用自动重监听:先把监听器重新置回 LISTEN。
|
||||
* ⚠ 内层 `state == CLOSED` 门是**必需的**,不是冗余:
|
||||
* `net_relisten_try` 无状态门,对已 LISTENING 的 fd 再 open+listen
|
||||
* 会让 ch395f_open_socket 返 USE,其失败分支会 ch395f_close_socket
|
||||
* —— 把正听着新 SYN 的 Socket 当场关掉(U12 同源事故)。
|
||||
* session_dead + 已回 LISTENING = 重建已由别处完成,这里只报 EOF。 */
|
||||
if ((p_sock->state == NET_SOCK_STATE_CLOSED) && (p_sock->auto_relisten != 0U)) {
|
||||
/* 非阻塞:失败由 D10 重试轮询接管 */
|
||||
(void)net_relisten_try(p_sock);
|
||||
}
|
||||
@@ -2073,6 +2133,11 @@ static int net_relisten_locked(net_sock_t *p_sock) {
|
||||
|
||||
/* 状态置回 LISTENING(fd 保留) */
|
||||
p_sock->state = NET_SOCK_STATE_LISTENING;
|
||||
/* N-32(P1,设计文档 §5 P1.1 的关键约定):此处**刻意不清 session_dead**。
|
||||
* 重建监听只是"芯片侧恢复就位",与"应用是否已确认上一条会话结束"是两件事;
|
||||
* 清了它,抢先 relisten 之后消费者就再也得不到 EOF(B10"消费者卡死"原型)。
|
||||
* session_dead 只由新 CONNECT 成功(handle_connect_event)或 memset
|
||||
* (net_close/net_socket)清零。 */
|
||||
/* 新监听不得继承上一条连接的释放原因(Trap 23 待办 #3) */
|
||||
p_sock->released_abnormal = 0U;
|
||||
/* N-30(Trap 25):清零前把对端端口转登为幽灵甄别基准 */
|
||||
@@ -2428,8 +2493,12 @@ static void net_pending_tick(void) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ③ 断连:排空 → 数据或 EOF(0) + 自动重监听 */
|
||||
if (p_sock->state == NET_SOCK_STATE_CLOSED) {
|
||||
/* ③ 断连:排空 → 数据或 EOF(0) + 自动重监听。
|
||||
* 判据同 net_recv_locked(N-32 P1 不变量 I):`state == CLOSED ||
|
||||
* session_dead`。只判 CLOSED 时,抢先 relisten 后 ①(要求 ESTABLISHED)
|
||||
* 与 ③(要求 CLOSED) 两头落空 → 挂起的 recv 只能干等 ② deadline 超时
|
||||
* (lftpd 阻塞 recv 的"消费者卡死"原型)。 */
|
||||
if ((p_sock->state == NET_SOCK_STATE_CLOSED) || (p_sock->session_dead != 0U)) {
|
||||
/* 查询残留字节数 */
|
||||
int rem = ch395f_get_recv_len((uint8_t)i);
|
||||
/* 有残留数据:按数据完成 */
|
||||
@@ -2445,8 +2514,12 @@ static void net_pending_tick(void) {
|
||||
/* 了结:以字节数通知 */
|
||||
net_pending_complete(i, rd);
|
||||
} else {
|
||||
/* 启用自动重监听:先把监听器重新置回 LISTEN */
|
||||
if (p_sock->auto_relisten != 0U) {
|
||||
/* 启用自动重监听:先把监听器重新置回 LISTEN。
|
||||
* ⚠ 内层 `state == CLOSED` 门同 net_recv_locked:session_dead
|
||||
* 但已回 LISTENING 时重建已由别处完成,再 open+listen 会因
|
||||
* USE 错误走进关闭分支、关掉正听着的 Socket(U12 同源) */
|
||||
if ((p_sock->state == NET_SOCK_STATE_CLOSED) &&
|
||||
(p_sock->auto_relisten != 0U)) {
|
||||
/* 非阻塞:失败由 D10 重试轮询接管 */
|
||||
(void)net_relisten_try(p_sock);
|
||||
}
|
||||
@@ -3575,6 +3648,10 @@ static void handle_connect_event(net_sock_t *p_sock) {
|
||||
p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8));
|
||||
/* 状态 → ESTABLISHED(监听自身转数据通道) */
|
||||
p_sock->state = NET_SOCK_STATE_ESTABLISHED;
|
||||
/* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平。必须在置
|
||||
* ESTABLISHED 之后立刻做:否则抢先 relisten 后到新连接的窗口内
|
||||
* recv 仍会误得 EOF(把新客户端踢开) */
|
||||
p_sock->session_dead = 0U;
|
||||
/* 新开 Socket 发送缓冲按空闲计 */
|
||||
p_sock->send_ready = 1;
|
||||
|
||||
@@ -3609,6 +3686,9 @@ static void handle_connect_event(net_sock_t *p_sock) {
|
||||
p_sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8));
|
||||
/* 状态 → ESTABLISHED */
|
||||
p_sock->state = NET_SOCK_STATE_ESTABLISHED;
|
||||
/* N-32(P1):新会话开始 —— 清"上一条会话已结束"电平(同 LISTENING
|
||||
* 分支:必须在转 ESTABLISHED 后立刻做,防抢先 relisten 窗口误 EOF) */
|
||||
p_sock->session_dead = 0U;
|
||||
|
||||
/* 使能 KeepAlive(客户端连接保活) */
|
||||
ch395f_set_keepalive_enable(p_sock->ch395_sock, 1);
|
||||
@@ -3695,6 +3775,11 @@ static void handle_disconnect_event(net_sock_t *p_sock) {
|
||||
}
|
||||
/* 正常释放(对端 FIN):断连了结回 EOF(0) */
|
||||
p_sock->released_abnormal = 0U;
|
||||
/* N-32(P1 不变量 I):置"本会话已结束"电平。须在置 CLOSED **之前**,
|
||||
* 使 session_dead 覆盖"CLOSED 等待 recv 排空"整个窗口——排空期间
|
||||
* recv 的 DONTWAIT 快路径先返数据(recv_len>0 优先),数据尽后判据
|
||||
* 落到本标志 → 仍得 EOF,不因排空慢而漏报 */
|
||||
p_sock->session_dead = 1U;
|
||||
/* N-30(Trap 25):登记本连接对端端口(幽灵态甄别基准,须在状态
|
||||
* 迁移前记录——remote_port 此刻仍有效,relisten 清零) */
|
||||
p_sock->last_peer_port = p_sock->remote_port;
|
||||
@@ -3787,6 +3872,10 @@ static void handle_timeout_event(net_sock_t *p_sock) {
|
||||
p_sock->released_abnormal = 1U;
|
||||
/* N-30(Trap 25):异常释放同样登记对端端口(幽灵甄别基准) */
|
||||
p_sock->last_peer_port = p_sock->remote_port;
|
||||
/* N-32(P1 不变量 I):异常结束同样是"本会话已结束"电平(TIMEOUT/RST
|
||||
* 已在下方 requarantine,本标志只表达"会话结束了"这一事实,与
|
||||
* released_abnormal(结束得好不好看)正交) */
|
||||
p_sock->session_dead = 1U;
|
||||
/* 状态 → CLOSED */
|
||||
p_sock->state = NET_SOCK_STATE_CLOSED;
|
||||
} else if (p_sock->state == NET_SOCK_STATE_LISTENING) {
|
||||
|
||||
@@ -172,6 +172,28 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen);
|
||||
*/
|
||||
int net_can_accept(int sockfd);
|
||||
|
||||
/*
|
||||
* 函数功能:查询监听 Socket 是否处于"应该尝试 accept"的就绪态(**纯 RAM 读,不发起 RPC**)
|
||||
* 入口参数:sockfd - 监听 Socket 描述符 int
|
||||
* 出口参数:无
|
||||
* 返回值:1 - 就绪(LISTENING,或 CLOSED + auto_relisten = 断连待重建态);0 - 不就绪 int
|
||||
* 限定条件:net_init() 已调用;任意任务上下文
|
||||
* 函数说明:1. N-32(P1 不变量 II):取代 `net_can_accept()`(只认 ESTABLISHED)作为
|
||||
* ui_net_sink 的 accept 门控——原门控使 :7003 永不进 `net_accept()`,
|
||||
* `net_lost_connect_probe`(丢 CONNECT 中断补 accept,N-30 实证的
|
||||
* "数据被吞/425"主根因修复)对该端口等于从未运行
|
||||
* 2. 放行 LISTENING:探针与 "closed-but-readable 接受" 都发生在
|
||||
* `net_accept_locked` **内部**,门控挡在外面就永远到不了;LISTENING 态
|
||||
* accept 要么被探针补中、要么返 WOULDBLOCK,两者均安全
|
||||
* 3. 放行 CLOSED + auto_relisten:单连接模型断连后 fd 停在 CLOSED 等
|
||||
* relisten(D10 非阻塞重试 / 对账④),此窗口内新 SYN 可能已被芯片
|
||||
* 接住 → 放行让 `net_accept_locked` 的状态门自行裁决
|
||||
* 4. 与 `net_can_accept` 的语义差异**有意为之**:本函数回答"这个监听器
|
||||
* 还在服役吗",而非"这一轮一定有连接"
|
||||
* 5. 跨任务读单字,同 `net_can_accept` 的最坏情形论证
|
||||
*/
|
||||
int net_listener_armed(int sockfd);
|
||||
|
||||
/*
|
||||
* 函数功能:查询 Socket 是否"值得发一次 recv"(**纯 RAM 读,不发起 RPC**)
|
||||
* 入口参数:sockfd - Socket 描述符 int
|
||||
|
||||
@@ -189,6 +189,22 @@ typedef struct {
|
||||
* 节拍 = accept 轮询 100ms;新监听/补 accept 后清零) */
|
||||
uint8_t lost_conn_seen;
|
||||
|
||||
/* N-32 / B10 根治 P1(2026-10-12,设计文档 §2.3 不变量 I):本会话"已结束"的
|
||||
* **电平**(粘滞),取代 `state == CLOSED` 这一**边沿**。
|
||||
* 为什么必须电平化:单连接复用模型下同一个 fd 既是监听器又是连接(handle
|
||||
* aliasing),"关掉一条连接"与"毁掉一个监听器"在类型上无法区分;而"会话结束"
|
||||
* 原先只能靠 `state == CLOSED` 这一刻被观察到——一旦别的路径抢先 relisten
|
||||
* 回 LISTENING(对账④/`net_accept_locked` 的 CLOSED 分支),消费者再 recv
|
||||
* 就恒得 WOULDBLOCK,EOF 永远失踪(B10"消费者卡死"与新连接被旧会话静默吸收)。
|
||||
* session_dead 让"会话已结束"可重复查询、幂等:谁先看到不重要,谁都饿不着。
|
||||
* 置位:handle_disconnect_event 释放分支 / handle_timeout_event ESTABLISHED
|
||||
* 分支 / net_connect_locked 命令被拒 / 对账①(复用前者,:654)。
|
||||
* 清零:新 CONNECT 成功(handle_connect_event 两支)、net_close/net_socket
|
||||
* (memset 整块清零)、net_relisten_locked 的 peer 簿记**不清**——
|
||||
* 这是关键:重建监听与"应用是否已确认会话结束"必须解耦(design §5 P1.1)。
|
||||
* 跨 fd 保留语义同 released_abnormal(幽灵/陈旧事件属芯片 Socket 级)。 */
|
||||
uint8_t session_dead;
|
||||
|
||||
/* 统计信息 */
|
||||
uint32_t recv_bytes; /* 累计接收字节数 */
|
||||
uint32_t send_bytes; /* 累计发送字节数 */
|
||||
|
||||
Reference in New Issue
Block a user