fix: 内存误判(psutil→GlobalMemoryStatusEx) + 时间戳硬截止(>30min丢弃)
- easyocr_worker.py: 删除psutil物理内存检查,统一使用GlobalMemoryStatusEx (Windows上psutil可比Task Manager少报~1.7GB,导致4.2GB可用误判为2.5GB) - monitor.py: _ocr_text_has_fresh_timestamp()加硬截止——最新时间戳>1800s(30min) 无论是否含CJK均丢弃,修复15:09看到13:34消息(90min前)被误回复 - 新增_timestamp_age_seconds()辅助方法
This commit is contained in:
+13
-14
@@ -99,20 +99,20 @@ try:
|
||||
|
||||
# ── 预检:物理内存+虚拟内存是否足够加载 CUDA 库 ──
|
||||
_vm = _get_virtual_memory_info()
|
||||
# 检查物理内存:CUDA加载需要足够物理内存,否则会使用页面文件导致冻结
|
||||
# ⭐ 统一使用 GlobalMemoryStatusEx(与 monitor.py / Task Manager 一致)
|
||||
# psutil.virtual_memory().available 在 Windows 上可比 Task Manager 少报 ~1.7GB,
|
||||
# 导致 4.2GB 可用时误判为 2.5GB 触发降级。
|
||||
if _vm:
|
||||
try:
|
||||
import psutil
|
||||
_mem = psutil.virtual_memory()
|
||||
_avail_phys_gb = _mem.available / (1024**3)
|
||||
_used_phys_gb = _mem.used / (1024**3)
|
||||
_total_phys_gb = _mem.total / (1024**3)
|
||||
# ⭐ 页面文件百分比 + 总提交量检查:防页面文件94%+时仍尝试CUDA导致冻结
|
||||
_pagefile_pct = _vm.get("commit_pct", 0) if _vm else 0
|
||||
_pagefile_used_gb = _total_phys_gb + (_vm.get("total_pagefile_gb", 0) - _vm.get("avail_pagefile_gb", 0)) - _avail_phys_gb if _vm else 0
|
||||
_commit_gb = _used_phys_gb + (_vm.get("total_pagefile_gb", 0) - _vm.get("avail_pagefile_gb", 0)) if _vm else _used_phys_gb
|
||||
_avail_phys_gb = _vm.get("avail_phys_gb", 0)
|
||||
_total_phys_gb = _vm.get("total_phys_gb", 0)
|
||||
_used_phys_gb = _total_phys_gb - _avail_phys_gb
|
||||
_pagefile_pct = _vm.get("commit_pct", 0)
|
||||
_pagefile_used_gb = _vm.get("total_pagefile_gb", 0) - _vm.get("avail_pagefile_gb", 0)
|
||||
_commit_gb = _used_phys_gb + _pagefile_used_gb
|
||||
if _pagefile_pct > 85.0 or _commit_gb > _total_phys_gb * 1.5:
|
||||
_msg = (
|
||||
f"系统内存压力过高: 物理剩余{_avail_phys_gb:.1f}GB({_mem.percent}%),"
|
||||
f"系统内存压力过高: 物理剩余{_avail_phys_gb:.1f}GB,"
|
||||
f"页面文件已用{_pagefile_pct}%,总提交{_commit_gb:.1f}GB>{_total_phys_gb*1.5:.1f}GB阈值"
|
||||
)
|
||||
_write_crash_log(f"{datetime.now()} [easyocr_worker] 系统内存压力预检失败: {_msg}")
|
||||
@@ -122,16 +122,15 @@ try:
|
||||
elif _avail_phys_gb < 3.0:
|
||||
_msg = (
|
||||
f"物理内存不足: 剩余{_avail_phys_gb:.1f}GB"
|
||||
f"(总计{_total_phys_gb:.1f}GB,使用{_mem.percent}%)"
|
||||
f"(总计{_total_phys_gb:.1f}GB)"
|
||||
f",CUDA加载需要≥3GB物理内存"
|
||||
)
|
||||
_write_crash_log(f"{datetime.now()} [easyocr_worker] 物理内存预检失败: {_msg}")
|
||||
# 设置环境变量强制CPU模式,而不是直接退出
|
||||
os.environ["EASYOCR_FORCE_CPU"] = "1"
|
||||
sys.stderr.write(f"[easyocr_worker] ⚠️ {_msg},降级CPU模式\n")
|
||||
sys.stderr.flush()
|
||||
except Exception:
|
||||
pass # psutil不可用时不阻断
|
||||
pass # GlobalMemoryStatusEx 失败时不阻断
|
||||
# ⭐ 检查页面文件:百分比>85% 或 剩余<2GB 时退出(防冻结)
|
||||
if _vm and (_vm.get("commit_pct", 0) > 85.0 or _vm.get("avail_pagefile_gb", 99) < 2.0):
|
||||
_msg = (
|
||||
|
||||
+54
@@ -3964,6 +3964,19 @@ class MessageMonitor:
|
||||
except Exception:
|
||||
pass
|
||||
if not newest_fresh_enough:
|
||||
# ⭐ 硬截止:最新时间戳 >30min → 无论是否含 CJK 均丢弃(同主路径)
|
||||
_hard_cutoff_s = 1800
|
||||
_newest_age_s = None
|
||||
for ts in timestamps:
|
||||
try:
|
||||
_age = self._timestamp_age_seconds(ts)
|
||||
if _age is not None and (_newest_age_s is None or _age < _newest_age_s):
|
||||
_newest_age_s = _age
|
||||
except Exception:
|
||||
pass
|
||||
if _newest_age_s is not None and _newest_age_s > _hard_cutoff_s:
|
||||
logger.debug(f"[ocr] 过滤器1结果: 过去日期+最新时间戳已过期>{_hard_cutoff_s}s({_newest_age_s:.0f}s),硬截止丢弃: {text[:80]!r}")
|
||||
return False
|
||||
if self._block_has_cjk_message(normalized):
|
||||
logger.debug(f"[ocr] 过滤器1结果: 过去日期+时间戳全过期但含中文消息内容(潜在新消息),放行交由下游去重: {text[:80]!r}")
|
||||
return True
|
||||
@@ -3998,6 +4011,20 @@ class MessageMonitor:
|
||||
except Exception:
|
||||
pass
|
||||
if not newest_fresh_enough:
|
||||
# ⭐ 硬截止:最新时间戳 >30min → 无论是否含 CJK 均丢弃
|
||||
# 修复:15:09 看到 13:34 消息(90min 前)因含 CJK 被放行触发回复
|
||||
_hard_cutoff_s = 1800 # 30 分钟
|
||||
_newest_age_s = None
|
||||
for ts in timestamps:
|
||||
try:
|
||||
_age = self._timestamp_age_seconds(ts)
|
||||
if _age is not None and (_newest_age_s is None or _age < _newest_age_s):
|
||||
_newest_age_s = _age
|
||||
except Exception:
|
||||
pass
|
||||
if _newest_age_s is not None and _newest_age_s > _hard_cutoff_s:
|
||||
logger.debug(f"[ocr] 过滤器1结果: 最新时间戳已过期>{_hard_cutoff_s}s({_newest_age_s:.0f}s),硬截止丢弃: {text[:80]!r}")
|
||||
return False
|
||||
if self._block_has_cjk_message(normalized):
|
||||
logger.debug(f"[ocr] 过滤器1结果: 时间戳全过期但含中文消息内容(潜在新消息),放行交由下游去重: {text[:80]!r}")
|
||||
return True
|
||||
@@ -4080,6 +4107,33 @@ class MessageMonitor:
|
||||
return False
|
||||
return diff > max_age
|
||||
|
||||
@staticmethod
|
||||
def _timestamp_age_seconds(ts_str: str):
|
||||
"""计算 HH:MM 或 HH:MM:SS 时间戳距现在的秒数。跨午夜处理。
|
||||
返回 None 表示无法解析。"""
|
||||
ts = ts_str.strip()
|
||||
if not ts:
|
||||
return None
|
||||
m = re.match(r'^(\d{1,2}):(\d{2})(?::(\d{2}))?$', ts)
|
||||
if not m:
|
||||
return None
|
||||
h, mi, s = int(m.group(1)), int(m.group(2)), int(m.group(3) or 0)
|
||||
if h > 23 or mi > 59 or s > 59:
|
||||
return None
|
||||
now = datetime.now()
|
||||
try:
|
||||
parsed = now.replace(hour=h, minute=mi, second=s, microsecond=0)
|
||||
except ValueError:
|
||||
return None
|
||||
diff = (now - parsed).total_seconds()
|
||||
if diff > 43200:
|
||||
parsed = parsed - timedelta(days=1)
|
||||
diff = (now - parsed).total_seconds()
|
||||
if diff < -43200:
|
||||
parsed = parsed - timedelta(days=1)
|
||||
diff = (now - parsed).total_seconds()
|
||||
return max(diff, 0)
|
||||
|
||||
@staticmethod
|
||||
def _is_system_message(text: str) -> bool:
|
||||
"""过滤机器人/系统消息,不作为用户消息处理。"""
|
||||
|
||||
Reference in New Issue
Block a user