fix: 修复停止按钮死循环+EasyOCR预热误触发+历史消息时间戳过滤错误 (Task #1638)

gui.py:
- 添加 _stop_in_progress 标志防止停止/启动死循环
- _toggle_monitor 检查 _stop_in_progress 标志防止重入
- _update_stop_ui 和 _stop_monitor_async 中正确设置和重置标志

monitor.py:
- 添加 _easyocr_warmup_generation 代际计数器防止死循环中旧预热线程错误初始化 EasyOCR
- 预热线程检查代际匹配防止跨 start/stop 周期的旧线程执行
- 移除 has_past 分支中的 skip_future_check=True,修复历史消息误判为新鲜的bug
  根因:skip_future_check 让未来时戳(如14:09看到21:15)通过检查,
  但OCR只能看到已发送消息,未来时戳必然来自过去日期
This commit is contained in:
youpaishidifu
2026-08-30 14:44:05 +08:00
parent 52013d28c1
commit 0552df5287
2 changed files with 24 additions and 3 deletions
+10
View File
@@ -601,6 +601,8 @@ class MsgBotApp(ctk.CTk):
# ⭐ 防止 _update_stop_ui() 中 deselect() 触发 _toggle_monitor() 回调的标志
self._suppress_toggle_callback = False
# ⭐ 防止停止/启动死循环:stop过程中禁止toggle重新触发start
self._stop_in_progress = False
self._build_ui()
@@ -811,6 +813,9 @@ class MsgBotApp(ctk.CTk):
# ⭐ 防止 _update_stop_ui() 中 deselect() 触发的回调
if self._suppress_toggle_callback:
return
# ⭐ 防止 stop 过程中被 re-toggle 导致死循环
if self._stop_in_progress:
return
enabled = self._toggle_btn.get()
if enabled:
@@ -832,6 +837,7 @@ class MsgBotApp(ctk.CTk):
self._ocr_manager.set_monitor_active(True)
else:
# ⭐ UI 立即更新;stop() 在后台线程执行,主线程保持响应,不再触发冻结弹窗
self._stop_in_progress = True # ⭐ 防止 stop 过程中被 re-toggle 导致死循环
self._status_label.configure(text="⏸ 已暂停")
self._log("⏸ 消息监控已暂停")
# 通知OCR管理器:监控已停止,开始计时
@@ -1451,11 +1457,15 @@ class MsgBotApp(ctk.CTk):
self.monitor.stop()
except Exception as e:
logger.warning(f"[gui] 后台 stop 异常: {e}")
finally:
# ⭐ stop 完成后在主线程重置标志,允许下一次 toggle
self.after(0, lambda: setattr(self, '_stop_in_progress', False))
def _update_stop_ui(self):
"""主线程更新急停后的 UI(stop 放到后台线程,主线程不冻结)。"""
# ⭐ 设置标志防止 deselect() 触发 _toggle_monitor() 回调
self._suppress_toggle_callback = True
self._stop_in_progress = True # ⭐ 防止死循环
try:
# 直接设置开关变量值,不触发回调
self._toggle_btn._variable.set(False)
+14 -3
View File
@@ -779,6 +779,8 @@ class MessageMonitor:
# ⭐ 停止信号事件:set() 后所有 time.sleep / 阻塞操作应立即响应退出
# 替代原 _running bool 标志:Event.wait(timeout) 可被 set() 唤醒,time.sleep 不行
self._stop_event = threading.Event()
# ⭐ EasyOCR 预热线程代际计数器:防止死循环中旧预热线程在新 start() 后醒来错误初始化 EasyOCR
self._easyocr_warmup_generation = 0
def _load_replied_texts_from_db(self):
"""从消息历史数据库加载最近的bot回复到 _replied_texts,
@@ -965,9 +967,13 @@ class MessageMonitor:
# 第3批:EasyOCR预热(延迟1s,最重的操作放最后)
if _HAS_EASYOCR and not self._easyocr_broken:
# ⭐ 代际计数器:防止死循环中旧预热线程在新 start() 后醒来错误初始化 EasyOCR
self._easyocr_warmup_generation += 1
_gen = self._easyocr_warmup_generation
def _delayed_easyocr_start():
time.sleep(1.0)
if self._running:
# ⭐ 双重检查:self._running + 代际匹配,防止死循环中的旧线程执行
if self._running and self._easyocr_warmup_generation == _gen:
self._gui_log("🔧 EasyOCR 子进程初始化中...")
self._ensure_easyocr_subprocess()
threading.Thread(target=_delayed_easyocr_start, daemon=True, name="easyocr-delay").start()
@@ -6651,8 +6657,13 @@ class MessageMonitor:
_has_fresh_ts = False
for ts in timestamps:
try:
# 过去日期+未来时间戳组合 → 日期识别错误 → 跳过未来时间戳检查
if not self._is_timestamp_old(ts, 300, skip_future_check=True):
# ⭐ Task #1638 修复:移除 skip_future_check=True
# 之前的 skip_future_check 逻辑假定"过去日期+未来时间戳=日期识别错误",
# 但OCR只能看到已发送的消息,未来时戳不可能来自今天。
# 例:14:09 看到 21:15 → 这是昨天的消息,不应放行。
# 正常 _is_timestamp_old 会用 60s 容差处理时钟偏移,
# 超过容差的"未来"时戳一律判为旧消息。
if not self._is_timestamp_old(ts, 300):
_has_fresh_ts = True
logger.debug(f"[ocr] 过滤器1结果: 过去日期+新鲜时间戳{ts}(≤300s),放行")
break