Files
DCU_ECAT_Master/_repro_hang.py
T

79 lines
2.4 KiB
Python
Raw Normal View History

2026-09-03 01:56:29 +08:00
"""_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)