Trap23两阶段修复: 异常释放400ms隔离+正常释放100ms沉降(仅收过数据), 修复中止后新传输首笔静默截断

This commit is contained in:
2026-09-25 01:57:03 +08:00
parent d0cbeb6b5b
commit a1f1f46262
2 changed files with 73 additions and 7 deletions
+36 -7
View File
@@ -1429,10 +1429,20 @@ static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags,
* NET_RELISTEN_SETTLE_MS 后强制放行,保证监听器不会永久失效。
* 正常 FIN 释放(handle_disconnect_event)不走此路径——既有行为零改动。
* 声明须早于 net_relisten_locked(其余重试状态见其后区块)。 */
#define NET_RELISTEN_QUARANTINE_MS 400U /* 隔离时长(等在途数据落尽 + 排空残渣) */
#define NET_RELISTEN_QUARANTINE_MS 400U /* 异常释放最短隔离(等在途数据落尽 + 排空残渣) */
#define NET_RELISTEN_GRACEFUL_MS 100U /* 正常释放最短沉降(等芯片完成上一条连接的收尾) */
#define NET_RELISTEN_SETTLE_MS 800U /* 隔离上限(须 < NET_RELISTEN_DEADLINE_MS,
* 否则隔离耗尽重试预算 → 监听被放弃,2026-09-23 实测) */
static uint8_t s_relisten_quarantine[NET_MAX_SOCKETS]; /* 1 = 本次释放为异常,需隔离 */
/* 重监听前"清芯片收尾状态"的两个等级(s_relisten_quarantine 取值):
* QUARANTINE - 异常释放(芯片 TIMEOUT):对端可能仍有在途数据,须清理 + 最短等 400ms;
* GRACEFUL - 正常释放(芯片 DISCONNECT):无在途数据,清理 + 最短等 100ms。
* 2026-09-24 台架实证:正常释放走的是既有"不复位芯片收尾状态"的路径,残留的 DISCONNECT
* 会在**复用同一 fd 的下一条连接**上被投递(accept 后约 10~20ms)→ 下一条上传被静默截断
* 到 2048B(一个芯片 RX 缓冲)且回报 226。只清理不等待(0ms)实测**无效**(4/4 仍截断),
* 说明芯片需要时间完成上一条连接的内部收尾;给足沉降即 4/4 通过。 */
#define NET_RELISTEN_SETTLE_QUARANTINE 1U
#define NET_RELISTEN_SETTLE_GRACEFUL 2U
static uint8_t s_relisten_quarantine[NET_MAX_SOCKETS]; /* 0=免清;1=异常释放;2=正常释放 */
static uint32_t s_relisten_settle_start[NET_MAX_SOCKETS]; /* 隔离起点(0 = 未开始) */
static uint32_t s_relisten_settle_dead[NET_MAX_SOCKETS]; /* 隔离上限到期时刻 */
@@ -1479,6 +1489,13 @@ static int net_relisten_locked(net_sock_t *p_sock) {
if (s_relisten_quarantine[sockfd] != 0U) {
uint8_t st[2] = {0, 0};
uint32_t now = HAL_GetTick();
/* 最短等待由释放来源决定(见 NET_RELISTEN_SETTLE_* 定义):异常释放等 400ms
* 让在途数据落尽;正常释放等 100ms 让芯片完成上一条连接的内部收尾。
* 日志按等级区分 quarantine(异常)/ settle(正常),便于台架分辨触发路径。 */
uint8_t lvl = s_relisten_quarantine[sockfd];
uint32_t hold = (lvl == NET_RELISTEN_SETTLE_QUARANTINE) ? NET_RELISTEN_QUARANTINE_MS
: NET_RELISTEN_GRACEFUL_MS;
const char *tag = (lvl == NET_RELISTEN_SETTLE_QUARANTINE) ? "quarantine" : "settle";
if (s_relisten_settle_start[sockfd] == 0U) {
s_relisten_settle_start[sockfd] = now;
@@ -1491,15 +1508,15 @@ static int net_relisten_locked(net_sock_t *p_sock) {
ch395f_get_socket_status((uint8_t)sockfd, st);
if (((st[0] != CH395F_SOCKET_CLOSED) || (st[1] != CH395F_TCP_CLOSED) ||
((uint32_t)(now - s_relisten_settle_start[sockfd]) < NET_RELISTEN_QUARANTINE_MS))) {
((uint32_t)(now - s_relisten_settle_start[sockfd]) < hold))) {
if ((int32_t)(now - s_relisten_settle_dead[sockfd]) < 0) {
DBG_INFO("sock%d relisten quarantine (sock=0x%02X tcp=0x%02X)",
sockfd, st[0], st[1]);
DBG_INFO("sock%d relisten %s (sock=0x%02X tcp=0x%02X)",
sockfd, tag, st[0], st[1]);
s_net_errno = NET_ERR_BUSY;
return -1; /* 交重试轮询(30ms 后复检) */
}
DBG_INFO("sock%d relisten quarantine timeout, force open (sock=0x%02X tcp=0x%02X)",
sockfd, st[0], st[1]);
DBG_INFO("sock%d relisten %s timeout, force open (sock=0x%02X tcp=0x%02X)",
sockfd, tag, st[0], st[1]);
}
s_relisten_quarantine[sockfd] = 0U; /* 隔离完成(或强制放行) */
s_relisten_settle_start[sockfd] = 0U;
@@ -2780,6 +2797,18 @@ static void handle_disconnect_event(net_sock_t *p_sock) {
/* 单连接:先不关 CH395F Socket,
* 留给 net_recv_locked 把剩余数据读出来再关,置 CLOSED 等待应用重新监听 */
DBG_INFO("sock%d DISCONNECTED, releasing (chip tcp=0x%02X)", p_sock->ch395_sock, st[1]);
/* Trap 23 推广(2026-09-24 台架实证):正常 DISCONNECT 释放留下的芯片收尾状态
* 会在复用同一 fd 的下一条连接上以"伪 DISCONNECT"形式投递(实测 accept 后约
* 10~20ms)→ 该连接被提前释放,应用把已到的 2048B 当整份文件并回报 226。
* 仅在**本连接收过数据**时登记沉降:实测触发源是"收过数据的数据通道被关闭",
* 而纯连接即关(tcp_reconnect_probe 的 0ms 复连用例)与只发不收的下载连接不残留
* —— 对它们免沉降即可保住 Trap 22 的快速复连时序(:21 @0ms ×50 全通过)。 */
if (p_sock->recv_bytes != 0U) {
int qfd = (int)(p_sock - s_net_socks);
if ((qfd >= 0) && (qfd < NET_MAX_SOCKETS)) {
s_relisten_quarantine[qfd] = NET_RELISTEN_SETTLE_GRACEFUL;
}
}
p_sock->state = NET_SOCK_STATE_CLOSED;
fire_event(p_sock, NET_EVENT_DISCONNECTED);
} else if (p_sock->state != NET_SOCK_STATE_CLOSED &&