#!/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 总预算应已耗尽:设备拆除该连接(修复版落 "time_sync: no TIME packet in 30s, drop conn" 日志,旧版静默)、 监听重建,:8888 应能再接受新连接; 4. 新连接发完整 8 字节 TIME 包 → 设备应落 "wall clock updated"。 用法: python e02_probe.py 退出码: 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 总预算耗尽(设备应拆除挂起连接、重建监听) remain = 34.0 - (time.time() - t0) if remain > 0: time.sleep(remain) # 4. 监听应已重建:新连接可接入,且正常对时路径工作 t2 = time.time() listener_ok = False try: ts2 = socket.create_connection((DEV, 8888), timeout=5) 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 except Exception as e: log("second time-sync conn FAIL: %r" % (e,)) try: ts.close() except OSError: pass # 设备已先拆连,本地 close 报 ECONNRESET 属预期 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)