修复Trap22快速复连丢中断致监听死亡:net层周期对账自愈(去抖+冷却),复现用例与回归全绿

This commit is contained in:
2026-09-23 02:27:10 +08:00
parent 3ac8eb807d
commit 77ee778605
3 changed files with 146 additions and 18 deletions
+105 -2
View File
@@ -412,6 +412,89 @@ int net_poll(void) {
return event_count;
}
/*
* 函数功能:芯片/本层 socket 状态周期对账(Trap 22 丢中断自愈)
* 限定条件:netTask 上下文(由 net_process_messages 每轮调用)
* 函数说明:芯片在快速复连场景会丢 CONNECT/DISCONNECT 中断;丢事件后本层无法靠事件
* 自愈——典型:对端连接一直占着单连接监听的"后备"(芯片 TCP 仍 ESTABLISHED),
* 后续 SYN 全被拒,而应用因未收到 DISCONNECT 仍以为有客户端(永不调用 accept,
* 事件驱动自愈无路可走)。故周期回读芯片状态,修正三类失同步:
* ① 本层 ESTABLISHED 而芯片已无连接 → 按断开处理(应用读到 EOF 后自动重监听)
* ② 本层 LISTENING 而芯片已完成握手 → 补做 standalone accept(复用 handle_connect_event)
* ③ 本层 LISTENING 而芯片监听已消失 → 重建监听(net_relisten_try)
* **去抖 + 冷却**:单次状态读可能落在"刚 open/listen 尚未生效"的瞬态
* (2026-09-23 实测:无去抖时监听被反复重建直至放弃),故同一失配需连续
* NET_RECONCILE_STABLE_N 次才动手;同一 socket 动手后冷却 NET_RECONCILE_COOLDOWN_MS。
* 周期 NET_RECONCILE_PERIOD_MS;每次每在用 socket 一条状态命令,开销可忽略。
*/
#define NET_RECONCILE_PERIOD_MS 200U /* 对账周期 */
#define NET_RECONCILE_STABLE_N 2U /* 连续失配次数(去抖,≈400ms) */
#define NET_RECONCILE_COOLDOWN_MS 1000U /* 同一 socket 两次动手的最小间隔(防抖成风暴) */
static void net_reconcile_tick(void) {
static uint32_t s_last = 0U;
static uint8_t s_bad[NET_MAX_SOCKETS]; /* 连续失配计数(去抖) */
static uint32_t s_cool[NET_MAX_SOCKETS]; /* 冷却到期时刻 */
uint32_t now = HAL_GetTick();
int i;
if ((uint32_t)(now - s_last) < NET_RECONCILE_PERIOD_MS) {
return;
}
s_last = now;
for (i = 0; i < NET_MAX_SOCKETS; i++) {
net_sock_t *p_sock = &s_net_socks[i];
uint8_t st[2] = {0, 0};
int bad = 0;
if ((p_sock->in_use == 0U) ||
((p_sock->state != NET_SOCK_STATE_ESTABLISHED) &&
(p_sock->state != NET_SOCK_STATE_LISTENING))) {
s_bad[i] = 0U;
continue;
}
ch395f_get_socket_status((uint8_t)i, st);
if ((p_sock->state == NET_SOCK_STATE_ESTABLISHED) &&
((st[0] == CH395F_SOCKET_CLOSED) || (st[1] == CH395F_TCP_CLOSED))) {
bad = 1; /* 芯片已无连接而本层以为还连着 */
} else if ((p_sock->state == NET_SOCK_STATE_LISTENING) &&
(st[1] == CH395F_TCP_ESTABLISHED)) {
bad = 2; /* 芯片已完成握手而无 CONNECT 中断 */
} else if ((p_sock->state == NET_SOCK_STATE_LISTENING) &&
((st[0] == CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_LISTEN))) {
bad = 3; /* 芯片监听已消失(异常关闭) */
}
if (bad == 0) {
s_bad[i] = 0U;
continue;
}
if (s_bad[i] < NET_RECONCILE_STABLE_N) {
s_bad[i]++;
}
if ((s_bad[i] < NET_RECONCILE_STABLE_N) ||
((int32_t)(now - s_cool[i]) < 0)) {
continue; /* 去抖未满,或处于冷却期:本轮不动手 */
}
s_bad[i] = 0U;
s_cool[i] = now + NET_RECONCILE_COOLDOWN_MS;
if (bad == 1) {
DBG_INFO("sock%d reconcile: chip closed (sock=0x%02X tcp=0x%02X) -> disconnect",
i, st[0], st[1]);
handle_disconnect_event(p_sock); /* 内置陈旧事件甄别 + 状态迁移 + 事件派发 */
} else if (bad == 2) {
DBG_INFO("sock%d reconcile: connection completed w/o CONNECT irq -> accept", i);
handle_connect_event(p_sock); /* 补做 standalone accept 簿记 */
} else {
DBG_INFO("sock%d reconcile: listen lost (sock=0x%02X tcp=0x%02X) -> relisten",
i, st[0], st[1]);
(void)net_relisten_try(p_sock);
}
}
}
/*
* 函数功能:处理消息队列中的请求
* 限定条件:必须在 netTask 上下文中调用(独占 CH395F 和 s_net_socks 访问权)
@@ -430,6 +513,7 @@ void net_process_messages(void) {
* L3:挂起 recv 的完成检查同位(两 tick 共享主循环节拍,均为 O(每 fd) 非阻塞)。 */
net_relisten_retry_tick();
net_pending_tick();
net_reconcile_tick(); /* Trap 22:芯片/本层状态周期对账(丢中断自愈) */
while (osMessageQueueGet(netMsgQueueHandle, &msg, NULL, 0) == osOK) {
int do_notify = 1;
@@ -851,7 +935,10 @@ static int net_accept_locked(int sockfd, struct net_sockaddr *addr, int *addrlen
net_poll();
/* 单连接:本 Socket 自身在 CONNECT 后已转为 ESTABLISHED,
* 只有处于 ESTABLISHED 才表示有可接受的连接;否则尚无连接 */
* 只有处于 ESTABLISHED 才表示有可接受的连接;否则尚无连接。
* 注:芯片丢中断导致的失同步不在此处自愈——单次状态读会误判"刚 listen 尚未生效",
* 曾致监听反复重建直至放弃(2026-09-23 实测风暴)。统一由 net_reconcile_tick()
* 周期对账(带去抖)处理。 */
if (p_listen_sock->state != NET_SOCK_STATE_ESTABLISHED) {
/* 连接已断开(CLOSED)且启用自动重监听:先把 Socket 重新置回 LISTEN,
* 下一次 accept 即可等待新连接(BSD 风格监听常驻) */
@@ -2560,7 +2647,23 @@ static void handle_disconnect_event(net_sock_t *p_sock) {
int sockfd = (int)(p_sock - s_net_socks);
if (p_sock->state == NET_SOCK_STATE_LISTENING) {
DBG_ERROR("WARNING: listen sock%d got disconnect", p_sock->ch395_sock);
/* Trap 22 / N-6:监听态收到 DISCONNECT —— 芯片在断开后会自动关闭该 Socket
* (FUN_PARA 默认行为),本层若不重开则**监听永久死亡**(此后所有 SYN 被拒、不自恢复)。
* 触发场景(2026-09-23 生产固件复现:FTP :21 断连后 100ms 快速复连,第 29 轮起全拒):
* 断连后 <~1s 的快速复连,芯片完成握手但 CONNECT 中断未上报(无 standalone accept,
* 控制块滞留 LISTENING),随后该连接断开 → DISCONNECT 落到本分支。
* 处置:回读芯片 socket 状态——已不在监听(芯片已关闭)即重建监听
* (net_relisten_try 走 D10 非阻塞重试,失败由重试轮询接管);仍在监听则判陈旧事件丢弃。 */
uint8_t st[2] = {0, 0};
ch395f_get_socket_status((uint8_t)sockfd, st);
if ((st[0] == CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_LISTEN)) {
DBG_INFO("sock%d listen lost (chip sock=0x%02X tcp=0x%02X), relisten now",
sockfd, st[0], st[1]);
(void)net_relisten_try(p_sock);
} else {
DBG_INFO("sock%d stale listen DISCONNECT ignored (chip tcp=0x%02X)", sockfd, st[1]);
}
} else if (p_sock->state == NET_SOCK_STATE_ESTABLISHED) {
/* 陈旧 DISCONNECT 甄别(D20 修复,2026-09-22 台架实证):
* 单连接模型下同一个 fd 会被"关闭 → 重新监听 → 再连接"循环复用,而上一条