fix(local-tool): DeviceGroup.__del__ access violation 全面修復
上一個 commit (a6a121a) 只修了 script 結束時的 cleanup,但使用者仍在 connect 重試路徑看到 access violation: connect attempt 1 failed → 新 connect attempt 2 → GC 回收 attempt 1 的舊 DeviceGroup → __del__ → kp_disconnect_devices 對已失效的 native handle → OSError: access violation 根因:`_device_group = None` 只是清掉 Python reference,舊物件的 __del__ 會延遲到下一次 GC cycle(可能發生在新 connect call 的 allocation 時), 此時 native handle 已 invalid。 修法: - 新增 `_clear_device_group()` helper:先 kp.core.disconnect_devices 把 native handle 正常釋放(errors silenced),再設 None - 全檔搜 `_device_group = None` 共 12 處,除了初始宣告(L40)和兩個 helper 自身(_clear_device_group / _cleanup)以外全部替換為 _clear_device_group() - 涵蓋所有 code path:connect retry / firmware load reconnect / disconnect handler / reset handler / error fallback Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a6a121ae86
commit
abbe9d4c0b
@ -38,6 +38,24 @@ except ImportError:
|
|||||||
|
|
||||||
# ── Global state ──────────────────────────────────────────────────────
|
# ── Global state ──────────────────────────────────────────────────────
|
||||||
_device_group = None
|
_device_group = None
|
||||||
|
|
||||||
|
|
||||||
|
def _clear_device_group():
|
||||||
|
"""Safely disconnect and clear the global _device_group.
|
||||||
|
|
||||||
|
KneronPLUS SDK's DeviceGroup.__del__ calls kp_disconnect_devices on the
|
||||||
|
native handle, but if the handle is already invalid (failed connect / stale
|
||||||
|
state) it causes 'OSError: access violation'. By explicitly disconnecting
|
||||||
|
before setting None, __del__ becomes a no-op on an already-disconnected
|
||||||
|
handle. All errors are silenced — this is best-effort cleanup.
|
||||||
|
"""
|
||||||
|
global _device_group
|
||||||
|
if _device_group is not None:
|
||||||
|
try:
|
||||||
|
kp.core.disconnect_devices(_device_group)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
_device_group = None
|
||||||
_model_id = None
|
_model_id = None
|
||||||
_model_nef = None
|
_model_nef = None
|
||||||
_model_input_size = 224 # updated on model load
|
_model_input_size = 224 # updated on model load
|
||||||
@ -732,7 +750,7 @@ def handle_connect(params):
|
|||||||
_log(f"KL720: Reconnected after firmware load, pid=0x{target_dev.product_id:04X}, fw={fw_str}")
|
_log(f"KL720: Reconnected after firmware load, pid=0x{target_dev.product_id:04X}, fw={fw_str}")
|
||||||
else:
|
else:
|
||||||
_log("WARNING: KL720 firmware files not found. Cannot operate with KDP legacy device.")
|
_log("WARNING: KL720 firmware files not found. Cannot operate with KDP legacy device.")
|
||||||
_device_group = None
|
_clear_device_group()
|
||||||
return {"error": "KL720 has legacy KDP firmware but KDP2 firmware files not found. "
|
return {"error": "KL720 has legacy KDP firmware but KDP2 firmware files not found. "
|
||||||
"Run update_kl720_firmware.py to flash KDP2 permanently."}
|
"Run update_kl720_firmware.py to flash KDP2 permanently."}
|
||||||
|
|
||||||
@ -756,9 +774,8 @@ def handle_connect(params):
|
|||||||
last_err = None
|
last_err = None
|
||||||
for attempt in range(max_retries):
|
for attempt in range(max_retries):
|
||||||
try:
|
try:
|
||||||
# Clear any stale device group from previous failed attempt
|
# Clear any stale device group from previous failed attempt.
|
||||||
# to prevent DeviceGroup.__del__ access violation during GC.
|
_clear_device_group()
|
||||||
_device_group = None
|
|
||||||
|
|
||||||
if use_without_check:
|
if use_without_check:
|
||||||
_log(f"{_device_chip}: connect_devices_without_check(usb_port_id={target_dev.usb_port_id}, connectable={target_dev.is_connectable}) attempt {attempt+1}/{max_retries}...")
|
_log(f"{_device_chip}: connect_devices_without_check(usb_port_id={target_dev.usb_port_id}, connectable={target_dev.is_connectable}) attempt {attempt+1}/{max_retries}...")
|
||||||
@ -774,7 +791,7 @@ def handle_connect(params):
|
|||||||
last_err = None
|
last_err = None
|
||||||
break
|
break
|
||||||
except Exception as conn_err:
|
except Exception as conn_err:
|
||||||
_device_group = None # prevent __del__ crash on stale handle
|
_clear_device_group()
|
||||||
last_err = conn_err
|
last_err = conn_err
|
||||||
_log(f"connect attempt {attempt+1} failed: {conn_err}")
|
_log(f"connect attempt {attempt+1} failed: {conn_err}")
|
||||||
if attempt < max_retries - 1:
|
if attempt < max_retries - 1:
|
||||||
@ -822,7 +839,7 @@ def handle_connect(params):
|
|||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
# Reconnect after firmware load (with retry)
|
# Reconnect after firmware load (with retry)
|
||||||
_device_group = None
|
_clear_device_group()
|
||||||
for retry in range(3):
|
for retry in range(3):
|
||||||
try:
|
try:
|
||||||
descs = kp.core.scan_devices()
|
descs = kp.core.scan_devices()
|
||||||
@ -861,7 +878,7 @@ def handle_connect(params):
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_device_group = None
|
_clear_device_group()
|
||||||
return {"error": str(e)}
|
return {"error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
@ -870,7 +887,7 @@ def handle_disconnect(params):
|
|||||||
global _device_group, _model_id, _model_nef, _firmware_loaded
|
global _device_group, _model_id, _model_nef, _firmware_loaded
|
||||||
global _model_type, _model_input_size, _device_chip
|
global _model_type, _model_input_size, _device_chip
|
||||||
|
|
||||||
_device_group = None
|
_clear_device_group()
|
||||||
_model_id = None
|
_model_id = None
|
||||||
_model_nef = None
|
_model_nef = None
|
||||||
_model_type = "tiny_yolov3"
|
_model_type = "tiny_yolov3"
|
||||||
@ -906,7 +923,7 @@ def handle_reset(params):
|
|||||||
# Even if it throws, the device usually does reset.
|
# Even if it throws, the device usually does reset.
|
||||||
|
|
||||||
# Clear all state — the device is gone until it re-enumerates.
|
# Clear all state — the device is gone until it re-enumerates.
|
||||||
_device_group = None
|
_clear_device_group()
|
||||||
_model_id = None
|
_model_id = None
|
||||||
_model_nef = None
|
_model_nef = None
|
||||||
_model_type = "tiny_yolov3"
|
_model_type = "tiny_yolov3"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user