"""_repro_hang.py — 复现 2026-09-03 01:40 "op/start 卡住→闪退"。 复刻 ecat.html 的 UI 操作序列(100ms 周期 + 2s pollSync + 手动 stop/start 节奏): open → 循环 N 轮{ op/start → 立即 cycle+sync_diag → cycle@100ms → ~2s 后 op/stop } → close faulthandler 45s 看门狗: 卡住时自动转储所有线程栈(op/start 有界 ~26s, 超过 45s = 真卡死)。 退出码非 0xC0000005 之外的异常退出也会打印。 """ import faulthandler import sys import time faulthandler.enable() # 捕获原生访问违例(SEH)并打印 Python 栈 = 闪退根因现场 faulthandler.dump_traceback_later(45, exit=False) import ecat_server as es def t(): return time.strftime("%H:%M:%S") def show(res): for line in res.get("log", []): print(f" [{t()}] {line}", flush=True) def main(): rounds = int(sys.argv[1]) if len(sys.argv) > 1 else 30 r = es.api_open({"substr": "I226"}) show(r) if not r.get("ok"): print("open 失败, 退出", flush=True) return 1 for i in range(rounds): print(f"=== round {i} op/start ===", flush=True) r = es.api_op_start({"dc_cycle_us": 0}) show(r) if not r.get("ok"): print(f" [{t()}] op/start 失败, 继续下一轮", flush=True) time.sleep(1.0) continue # 复刻 doCycle() 立即一次 + pollSync() 立即一次 r = es.api_op_cycle({"outputs": ""}) if not r.get("ok"): show(r) r = es.api_sync_diag({}) if not r.get("ok"): show(r) # 100ms 周期跑 ~2s, 中途 2s 处再 pollSync 一次 end = time.time() + 2.0 synced = False while time.time() < end: r = es.api_op_cycle({"outputs": ""}) if not r.get("ok"): show(r) break if not synced and time.time() > end - 1.0: synced = True r = es.api_sync_diag({}) if not r.get("ok"): show(r) time.sleep(0.1) print(f" [{t()}] op/stop", flush=True) r = es.api_op_stop({}) show(r) time.sleep(1.0) # 用户点击间隔 es.api_close({}) print(f"[{t()}] 全部 {rounds} 轮完成, 无卡死", flush=True) faulthandler.cancel_dump_traceback_later() return 0 if __name__ == "__main__": rc = main() print(f"EXIT rc={rc}", flush=True) sys.exit(rc)