2025-09-22 19:10:58 +08:00
|
|
|
|
import ctypes
|
2025-09-17 22:23:57 +08:00
|
|
|
|
import threading
|
2025-10-24 22:04:28 +08:00
|
|
|
|
import time
|
2025-10-25 01:18:41 +08:00
|
|
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
2025-09-19 19:39:32 +08:00
|
|
|
|
from typing import Dict, Tuple, List
|
2025-09-22 19:10:58 +08:00
|
|
|
|
|
2025-08-06 22:11:33 +08:00
|
|
|
|
from Utils.LogManager import LogManager
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-10-24 22:04:28 +08:00
|
|
|
|
def _async_raise(tid: int, exc_type=KeyboardInterrupt) -> bool:
|
2025-10-25 01:18:41 +08:00
|
|
|
|
"""向指定线程抛异常"""
|
|
|
|
|
|
if not tid:
|
|
|
|
|
|
LogManager.method_error(f"强杀失败: 线程ID为空", "task")
|
|
|
|
|
|
return False
|
2025-10-24 22:04:28 +08:00
|
|
|
|
try:
|
|
|
|
|
|
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
|
|
|
|
|
|
ctypes.c_long(tid), ctypes.py_object(exc_type)
|
|
|
|
|
|
)
|
|
|
|
|
|
if res == 0:
|
|
|
|
|
|
LogManager.method_info(f"线程 {tid} 不存在", "task")
|
|
|
|
|
|
return False
|
|
|
|
|
|
elif res > 1:
|
|
|
|
|
|
ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), 0)
|
|
|
|
|
|
LogManager.method_info(f"线程 {tid} 命中多个线程,已回滚", "task")
|
|
|
|
|
|
return False
|
|
|
|
|
|
return True
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
LogManager.method_error(f"强杀线程失败: {e}", "task")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
2025-09-22 19:10:58 +08:00
|
|
|
|
|
2025-09-17 22:23:57 +08:00
|
|
|
|
class ThreadManager:
|
2025-09-18 20:09:52 +08:00
|
|
|
|
_tasks: Dict[str, Dict] = {}
|
2025-10-24 22:04:28 +08:00
|
|
|
|
_lock = threading.RLock()
|
|
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
|
def _cleanup_if_dead(cls, udid: str):
|
|
|
|
|
|
obj = cls._tasks.get(udid)
|
|
|
|
|
|
if obj and not obj["thread"].is_alive():
|
|
|
|
|
|
cls._tasks.pop(udid, None)
|
|
|
|
|
|
LogManager.method_info(f"检测到 [{udid}] 线程已结束,自动清理。", "task")
|
2025-08-06 22:11:33 +08:00
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2025-10-24 22:04:28 +08:00
|
|
|
|
def add(cls, udid: str, thread: threading.Thread, event: threading.Event, force: bool = False) -> Tuple[int, str]:
|
2025-09-18 20:09:52 +08:00
|
|
|
|
with cls._lock:
|
2025-10-24 22:04:28 +08:00
|
|
|
|
cls._cleanup_if_dead(udid)
|
|
|
|
|
|
old = cls._tasks.get(udid)
|
|
|
|
|
|
if old and old["thread"].is_alive():
|
|
|
|
|
|
if not force:
|
|
|
|
|
|
return 1001, "当前设备已存在任务"
|
|
|
|
|
|
LogManager.method_info(f"[{udid}] 检测到旧任务,尝试强制停止", "task")
|
|
|
|
|
|
cls._force_stop_locked(udid)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
thread.start()
|
2025-10-25 01:18:41 +08:00
|
|
|
|
# 等待 ident 初始化
|
|
|
|
|
|
for _ in range(10):
|
|
|
|
|
|
if thread.ident:
|
|
|
|
|
|
break
|
|
|
|
|
|
time.sleep(0.05)
|
|
|
|
|
|
|
2025-10-24 22:04:28 +08:00
|
|
|
|
cls._tasks[udid] = {
|
|
|
|
|
|
"id": thread.ident,
|
|
|
|
|
|
"thread": thread,
|
|
|
|
|
|
"event": event,
|
|
|
|
|
|
"start_time": time.time(),
|
|
|
|
|
|
}
|
|
|
|
|
|
LogManager.method_info(f"创建任务成功 [{udid}],线程ID={thread.ident}", "task")
|
|
|
|
|
|
return 200, "创建成功"
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
LogManager.method_error(f"线程启动失败: {e}", "task")
|
|
|
|
|
|
return 1002, f"线程启动失败: {e}"
|
2025-08-06 22:11:33 +08:00
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2025-10-24 22:04:28 +08:00
|
|
|
|
def stop(cls, udid: str, stop_timeout: float = 5.0, kill_timeout: float = 2.0) -> Tuple[int, str]:
|
|
|
|
|
|
with cls._lock:
|
|
|
|
|
|
obj = cls._tasks.get(udid)
|
|
|
|
|
|
if not obj:
|
|
|
|
|
|
return 200, "任务不存在"
|
|
|
|
|
|
|
|
|
|
|
|
thread = obj["thread"]
|
|
|
|
|
|
event = obj["event"]
|
|
|
|
|
|
tid = obj["id"]
|
|
|
|
|
|
|
|
|
|
|
|
LogManager.method_info(f"请求停止 [{udid}] 线程ID={tid}", "task")
|
|
|
|
|
|
|
|
|
|
|
|
if not thread.is_alive():
|
2025-09-22 19:10:58 +08:00
|
|
|
|
cls._tasks.pop(udid, None)
|
2025-10-24 22:04:28 +08:00
|
|
|
|
return 200, "已结束"
|
|
|
|
|
|
|
2025-10-25 01:18:41 +08:00
|
|
|
|
# 协作式停止
|
2025-10-24 22:04:28 +08:00
|
|
|
|
try:
|
|
|
|
|
|
event.set()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
LogManager.method_error(f"[{udid}] 设置停止事件失败: {e}", "task")
|
|
|
|
|
|
|
2025-10-25 01:18:41 +08:00
|
|
|
|
def _wait_stop():
|
2025-10-27 21:44:16 +08:00
|
|
|
|
# 先给 1 秒高频检查机会(很多 I/O 点会在这个窗口立刻感知到)
|
|
|
|
|
|
t0 = time.time()
|
|
|
|
|
|
while time.time() - t0 < 1.0 and thread.is_alive():
|
|
|
|
|
|
time.sleep(0.05)
|
|
|
|
|
|
|
|
|
|
|
|
# 再进入原有的 join 窗口
|
2025-10-24 22:04:28 +08:00
|
|
|
|
thread.join(timeout=stop_timeout)
|
2025-10-25 01:18:41 +08:00
|
|
|
|
if thread.is_alive():
|
|
|
|
|
|
LogManager.method_info(f"[{udid}] 协作超时 -> 尝试强杀", "task")
|
2025-10-27 21:44:16 +08:00
|
|
|
|
try:
|
|
|
|
|
|
_async_raise(tid) # 兜底:依然保留你的策略
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
LogManager.method_error(f"[{udid}] 强杀触发失败: {e}", "task")
|
2025-10-24 22:04:28 +08:00
|
|
|
|
thread.join(timeout=kill_timeout)
|
|
|
|
|
|
|
|
|
|
|
|
if not thread.is_alive():
|
2025-10-25 01:18:41 +08:00
|
|
|
|
LogManager.method_info(f"[{udid}] 停止成功", "task")
|
|
|
|
|
|
else:
|
|
|
|
|
|
LogManager.method_error(f"[{udid}] 停止失败(线程卡死),已清理占位", "task")
|
|
|
|
|
|
|
|
|
|
|
|
with cls._lock:
|
2025-10-24 22:04:28 +08:00
|
|
|
|
cls._tasks.pop(udid, None)
|
|
|
|
|
|
|
2025-10-25 01:18:41 +08:00
|
|
|
|
threading.Thread(target=_wait_stop, daemon=True).start()
|
|
|
|
|
|
return 200, "停止请求已提交"
|
2025-09-17 22:23:57 +08:00
|
|
|
|
|
2025-09-19 19:39:32 +08:00
|
|
|
|
@classmethod
|
2025-10-24 22:04:28 +08:00
|
|
|
|
def _force_stop_locked(cls, udid: str):
|
|
|
|
|
|
obj = cls._tasks.get(udid)
|
|
|
|
|
|
if not obj:
|
|
|
|
|
|
return
|
2025-09-22 19:10:58 +08:00
|
|
|
|
try:
|
2025-10-24 22:04:28 +08:00
|
|
|
|
event = obj["event"]
|
|
|
|
|
|
event.set()
|
|
|
|
|
|
obj["thread"].join(timeout=2)
|
|
|
|
|
|
if obj["thread"].is_alive():
|
|
|
|
|
|
_async_raise(obj["id"])
|
|
|
|
|
|
obj["thread"].join(timeout=1)
|
2025-09-22 19:10:58 +08:00
|
|
|
|
except Exception as e:
|
2025-10-24 22:04:28 +08:00
|
|
|
|
LogManager.method_error(f"[{udid}] 强制停止失败: {e}", "task")
|
|
|
|
|
|
finally:
|
|
|
|
|
|
cls._tasks.pop(udid, None)
|
2025-09-19 19:39:32 +08:00
|
|
|
|
|
2025-09-18 20:09:52 +08:00
|
|
|
|
@classmethod
|
2025-10-24 22:04:28 +08:00
|
|
|
|
def batch_stop(cls, ids: List[str]) -> Tuple[int, str]:
|
|
|
|
|
|
failed = []
|
2025-10-25 01:18:41 +08:00
|
|
|
|
with ThreadPoolExecutor(max_workers=4) as executor:
|
|
|
|
|
|
futures = {executor.submit(cls.stop, udid): udid for udid in ids}
|
|
|
|
|
|
for future in as_completed(futures):
|
2025-10-27 21:44:16 +08:00
|
|
|
|
udid = futures[future]
|
|
|
|
|
|
try:
|
|
|
|
|
|
code, msg = future.result()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
LogManager.method_error(f"[{udid}] stop 调用异常: {e}", "task")
|
|
|
|
|
|
failed.append(udid)
|
|
|
|
|
|
continue
|
2025-10-25 01:18:41 +08:00
|
|
|
|
if code != 200:
|
2025-10-27 21:44:16 +08:00
|
|
|
|
failed.append(udid)
|
2025-10-24 22:04:28 +08:00
|
|
|
|
if failed:
|
2025-10-25 01:18:41 +08:00
|
|
|
|
return 207, f"部分任务停止失败: {failed}"
|
|
|
|
|
|
return 200, "全部停止请求已提交"
|