a12367981d
48 轮直读 SINT 矩阵(8 周期×6 变体)复核历史"丢中断"说法:芯片侧事件 丢失 = 0。归类:drain 读清消费 3/48(drain-int 探针现场抓获)、门控 竞态 2/48、主机同步 1/48、幽灵态 0/48 未复现。 根除性修复:net_listen_locked / net_listener_try 的排事件读清从 listen 后挪至 LISTEN 命令之前——新连接 CONNECT 落进该窗即被本方读清 吞掉,即"数据被吞"的生产侧自伤机制(台架 3/48 为同形态放大版)。 全局读收口:全局中断寄存器唯一读者 = net_poll(I7);生产/测试/台架 游离全局读全部删除,ch395f.h 声明处加护栏注释。 归因更正:17 文件约 40 处"丢中断"措辞改为"事件未收到/读清消费竞态" (机制全部保留,仅归因更正);设计文档 §5.2 新增 E14 锚点;Trap Records TRAP-NET-17/-20/B3 归因更正段;配图生成器与 SVG 重生成。 台架:事件未收到复核探针保留(drain-int / SINT 补读×3 / 超时归类), 下次跑矩阵自动归类;实验注册(main.c/uvprojx)已完全还原。 验证:@build 0 错误 0 警告;ci_local L0 32/0 + gitlint + L0.5 953/953;doc-links --all --sections-strict 通过;@test PASS(无 [ERR],net init 619ms);回归 :7003 300ms×10 = 10/10、:21 n30×50 = 0/50 失败。
111 lines
4.5 KiB
Python
111 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
||
"""E-02 头阻塞专项台架探针(timeSync 侧,A/B 对照用)。
|
||
|
||
场景(错误码传播审计.md E-02 的 timeSync 触发形态):
|
||
1. 一个"挂起"的对时客户端连上 :8888 后不发任何数据(空闲/故障客户端,
|
||
常见工况)——旧固件在此触发 30s 阻塞 net_recv 独占 netTask,
|
||
全体消费者的队列消息停摆最长 30s;
|
||
2. 2s 后从 PC 发起 FTP(:21) 连接,测 banner 到达时延 = 队列响应性:
|
||
修复前 ≈ 25~30s(队头冻结),修复后 ≈ 数百 ms 内;
|
||
3. 34s 时 30s 总预算应已耗尽:设备拆掉该会话(自愈收敛阶段 3 net_drop_conn:
|
||
FIN 关闭 + 本层收口 + 重监听),随后**本探针模拟守规矩对端**:
|
||
close 自己的 socket;
|
||
4. 芯片回 CLOSED(TC-NET-204 模式 A:TIME_WAIT 过渡 ~150ms)→ relisten,
|
||
:8888 应能再接受新连接(3s 重试窗内);新连接发完整 8 字节 TIME 包 →
|
||
设备应落 "wall clock updated"。
|
||
|
||
⚠ 定案口径(TRAP-NET-07 定案更正 2026-10-11 + 《网络自愈架构设计》§5.2 E13):CH395F close
|
||
**无 RST 语义**。若对端内核存活(app 挂起但 socket 不关,内核自动 ACK FIN),
|
||
芯片停在 FIN_WAIT_2、监听恢复受限于对端 close——探针第 3 步的 close 正是
|
||
模拟"对端配合"这一可恢复边界;不配合场景的兜底是芯片 TIMEOUT/keepalive
|
||
拆僵尸会话(E13 定案,30~90s)。
|
||
|
||
用法: python e02_probe.py <run-tag>
|
||
退出码: 0 = 队列响应性 PASS;1 = FAIL;2 = 探针自身异常
|
||
"""
|
||
import socket
|
||
import sys
|
||
import time
|
||
|
||
DEV = "192.168.1.100"
|
||
tag = sys.argv[1] if len(sys.argv) > 1 else "run"
|
||
|
||
t0 = time.time()
|
||
|
||
|
||
def log(msg):
|
||
print("[%s] %9.3f %s" % (tag, time.time() - t0, msg), flush=True)
|
||
|
||
|
||
def main():
|
||
# 1. 挂起对时客户端:连接后不发任何数据
|
||
ts = socket.create_connection((DEV, 8888), timeout=5)
|
||
log("time-sync conn open (hung client: sends nothing)")
|
||
|
||
# 2. 2s 后测 FTP banner 时延(队列响应性)
|
||
time.sleep(2.0)
|
||
t1 = time.time()
|
||
banner = None
|
||
try:
|
||
ftp = socket.create_connection((DEV, 21), timeout=35)
|
||
banner = ftp.recv(256)
|
||
dt = time.time() - t1
|
||
log("FTP banner after %8.1f ms: %r" % (dt * 1000, banner[:40]))
|
||
ftp.close()
|
||
except Exception as e:
|
||
dt = time.time() - t1
|
||
log("FTP connect FAIL after %8.1f ms: %r" % (dt * 1000, e))
|
||
dt = None
|
||
queue_ok = (banner is not None) and (dt is not None) and (dt < 3.0)
|
||
log("queue responsiveness: %s (banner %s ms)"
|
||
% ("PASS" if queue_ok else "FAIL",
|
||
("%.0f" % (dt * 1000)) if dt is not None else "n/a"))
|
||
|
||
# 3. 等 30s 总预算耗尽(设备应拆会话:FIN + 本层收口 + 重监听)
|
||
remain = 34.0 - (time.time() - t0)
|
||
if remain > 0:
|
||
time.sleep(remain)
|
||
|
||
# 3b. 模拟守规矩对端:设备已发 FIN(drop_conn),本端 close 配合拆链。
|
||
# CH395F close 无 RST 语义(TRAP-NET-07 定案更正):不配合的内核存活对端
|
||
# 会把芯片停在 FIN_WAIT_2,监听恢复受限于对端 close——不在本探针判据内。
|
||
try:
|
||
ts.close()
|
||
log("hung client socket closed (cooperate with device FIN)")
|
||
except OSError:
|
||
pass # 设备已先拆连,本地 close 报 ECONNRESET 属预期
|
||
|
||
# 4. 监听应已重建(芯片 TIME_WAIT 过渡 ~150ms + relisten):3s 重试窗
|
||
t2 = time.time()
|
||
listener_ok = False
|
||
ts2 = None
|
||
while time.time() - t2 < 3.0:
|
||
try:
|
||
ts2 = socket.create_connection((DEV, 8888), timeout=2)
|
||
break
|
||
except Exception as e:
|
||
log("second time-sync conn retry: %r" % (e,))
|
||
time.sleep(0.3)
|
||
if ts2 is not None:
|
||
dt2 = time.time() - t2
|
||
epoch = int(time.time())
|
||
log("new time-sync conn in %6.0f ms, sent full TIME packet (epoch=%d)"
|
||
% (dt2 * 1000, epoch))
|
||
ts2.sendall(b"TIME" + epoch.to_bytes(4, "big"))
|
||
time.sleep(1.5) # 留时间给设备处理并打日志
|
||
ts2.close()
|
||
listener_ok = True
|
||
else:
|
||
log("second time-sync conn FAIL (3s window)")
|
||
log("OVERALL: queue=%s listener=%s"
|
||
% ("PASS" if queue_ok else "FAIL", "PASS" if listener_ok else "FAIL"))
|
||
return 0 if (queue_ok and listener_ok) else 1
|
||
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
sys.exit(main())
|
||
except Exception as e:
|
||
print("[%s] PROBE ERROR: %r" % (tag, e), flush=True)
|
||
sys.exit(2)
|