Merge branch 'dev'
This commit is contained in:
+58
-4
@@ -22,9 +22,11 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import threading
|
import threading
|
||||||
import subprocess
|
import subprocess
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import (ThreadPoolExecutor, as_completed,
|
||||||
|
TimeoutError as FuturesTimeout)
|
||||||
|
|
||||||
from config import APK_DIR, ADB_PATH
|
from config import APK_DIR, ADB_PATH
|
||||||
from core.logger import get_logger
|
from core.logger import get_logger
|
||||||
@@ -35,6 +37,9 @@ _log = get_logger("core.apk")
|
|||||||
|
|
||||||
# adb install 超时(秒)。大 APK 安装慢,给 5 分钟
|
# adb install 超时(秒)。大 APK 安装慢,给 5 分钟
|
||||||
_INSTALL_TIMEOUT = 300
|
_INSTALL_TIMEOUT = 300
|
||||||
|
# 分块推送"多久没有任何字节被接收"就算链路卡死(秒)。注意这是**停滞**判据,
|
||||||
|
# 不是总时长:正常推送会不断刷新它。
|
||||||
|
_PUSH_STALL_TIMEOUT = 90
|
||||||
# 并发安装数
|
# 并发安装数
|
||||||
_INSTALL_CONCURRENCY = 5
|
_INSTALL_CONCURRENCY = 5
|
||||||
|
|
||||||
@@ -333,7 +338,10 @@ class ApkManager:
|
|||||||
f"跳过={len(serials)-len(install_list)}")
|
f"跳过={len(serials)-len(install_list)}")
|
||||||
|
|
||||||
if install_list:
|
if install_list:
|
||||||
with ThreadPoolExecutor(max_workers=_INSTALL_CONCURRENCY) as pool:
|
# 不用 with(它退出时会 wait 卡住的线程):超时后要能立刻收尾,
|
||||||
|
# 否则界面永远停在"安装中",后续安装全被"已有安装任务在进行"挡住
|
||||||
|
pool = ThreadPoolExecutor(max_workers=_INSTALL_CONCURRENCY)
|
||||||
|
try:
|
||||||
futures = {pool.submit(self._install_one, s, apk_path,
|
futures = {pool.submit(self._install_one, s, apk_path,
|
||||||
package_name): s
|
package_name): s
|
||||||
for s in install_list}
|
for s in install_list}
|
||||||
@@ -343,8 +351,23 @@ class ApkManager:
|
|||||||
fut.result()
|
fut.result()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._set_item(s, "failed", f"安装异常: {e}")
|
self._set_item(s, "failed", f"安装异常: {e}")
|
||||||
|
except FuturesTimeout:
|
||||||
|
_log.warning(f"安装任务 {apk_name}: 有设备超时未返回,先收尾")
|
||||||
|
for s in install_list:
|
||||||
|
it = ((self._install_task or {}).get("items") or {}).get(s) or {}
|
||||||
|
if it.get("status") in ("pending", "installing"):
|
||||||
|
self._set_item(s, "failed", "安装超时(设备无响应)")
|
||||||
|
except Exception as e:
|
||||||
|
_log.exception(f"安装任务 {apk_name} 异常")
|
||||||
|
for s in install_list:
|
||||||
|
it = ((self._install_task or {}).get("items") or {}).get(s) or {}
|
||||||
|
if it.get("status") in ("pending", "installing"):
|
||||||
|
self._set_item(s, "failed", f"安装异常: {e}")
|
||||||
|
finally:
|
||||||
|
pool.shutdown(wait=False)
|
||||||
|
|
||||||
# 标记完成
|
# 标记完成(**必须**落到这里:没置 finished 的安装任务会一直挂着,
|
||||||
|
# 让"已有安装任务正在进行"永久挡住后续安装 —— 2026-09-14 事故)
|
||||||
if self._install_task:
|
if self._install_task:
|
||||||
self._install_task["finished"] = True
|
self._install_task["finished"] = True
|
||||||
# 统计
|
# 统计
|
||||||
@@ -498,6 +521,33 @@ class ApkManager:
|
|||||||
_log.warning(f"[{serial}] exec-in 启动失败,退回 adb push: {e}")
|
_log.warning(f"[{serial}] exec-in 启动失败,退回 adb push: {e}")
|
||||||
return self._push_fallback(serial, apk_path, remote)
|
return self._push_fallback(serial, apk_path, remote)
|
||||||
|
|
||||||
|
# 看门狗:链路卡死时 `proc.stdin.write()` 会**永久阻塞**(设备掉线、
|
||||||
|
# WiFi 断了、任务在抢 adb…)→ 线程挂死 → 整批安装永不 finished → 界面
|
||||||
|
# 卡在"安装中"、后续安装全被"已有安装任务在进行"挡住(2026-09-14 事故)。
|
||||||
|
# 这里盯住"已写字节数"是否还在推进,停滞超时就 kill 掉 adb —— 阻塞中的
|
||||||
|
# write 会立刻以异常退出,走下面的回退逻辑。
|
||||||
|
# 为什么用看门狗而不是非阻塞写:`os.set_blocking`/管道 select 是 Unix 专有,
|
||||||
|
# 开发机是 Windows,看门狗两边都能用。
|
||||||
|
stop_watch = threading.Event()
|
||||||
|
|
||||||
|
def _stall_watchdog():
|
||||||
|
last_seen, since = -1, time.time()
|
||||||
|
while not stop_watch.wait(2.0):
|
||||||
|
if sent == total:
|
||||||
|
return
|
||||||
|
if sent != last_seen:
|
||||||
|
last_seen, since = sent, time.time()
|
||||||
|
elif time.time() - since > _PUSH_STALL_TIMEOUT:
|
||||||
|
_log.warning(f"[{serial}] 推送停滞 {int(time.time() - since)} 秒"
|
||||||
|
f"({sent}/{total} 字节),杀掉推送进程并回退")
|
||||||
|
try:
|
||||||
|
proc.kill()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
watchdog = threading.Thread(target=_stall_watchdog, daemon=True)
|
||||||
|
watchdog.start()
|
||||||
try:
|
try:
|
||||||
with open(apk_path, "rb") as f:
|
with open(apk_path, "rb") as f:
|
||||||
while True:
|
while True:
|
||||||
@@ -523,7 +573,11 @@ class ApkManager:
|
|||||||
proc.kill()
|
proc.kill()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return False, f"推送中断: {e}"
|
_log.warning(f"[{serial}] exec-in 推送中断({e}),已传 {sent}/{total} 字节,"
|
||||||
|
f"退回 adb push 重传")
|
||||||
|
return self._push_fallback(serial, apk_path, remote)
|
||||||
|
finally:
|
||||||
|
stop_watch.set()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rc = proc.wait(timeout=_INSTALL_TIMEOUT)
|
rc = proc.wait(timeout=_INSTALL_TIMEOUT)
|
||||||
|
|||||||
Reference in New Issue
Block a user