fix: telemetry single persistent connection + lock (kill concurrent write lock)

This commit is contained in:
2026-07-16 08:47:23 -04:00
parent 766592d855
commit 065c8ae1b9
3 changed files with 85 additions and 27 deletions
+36 -27
View File
@@ -8,13 +8,11 @@ from __future__ import annotations
import asyncio
import sqlite3
from typing import TYPE_CHECKING
import threading
from pathlib import Path
from loguru import logger
if TYPE_CHECKING:
from pathlib import Path
class SQLiteTelemetryRecorder:
"""基于 SQLite 的 LLM 调用遥测记录器。
@@ -57,16 +55,30 @@ class SQLiteTelemetryRecorder:
"""
def __init__(self, db_path: Path) -> None:
self._db_path = db_path
self._table_ready = False
"""建单持久连接 + 进程内 Lock(对齐 app/harness/log.py:HarnessLog 并发写模式)。
def _ensure_table(self, conn: sqlite3.Connection) -> None:
"""懒初始化:首次写入时创建 llm_calls 表。"""
if self._table_ready:
return
conn.execute(self._CREATE_TABLE_SQL)
conn.commit()
self._table_ready = True
把并发控制拉到进程内(threading.Lock 串行化写),消除"每次新连接并发写同一
db、靠 SQLite busy_timeout 跨连接协调"在高频下撑爆 timeout → database is locked
的根因。check_same_thread=Falserecord_llm_call 经 asyncio.to_thread 在线程池
不同线程调用,共享连接跨线程访问需此 flag,串行性由 self._lock 保证。
遥测哲学(P5):连接初始化失败降级不冒泡(self._conn=None,写入直接丢弃 warning),
绝不因遥测故障拖垮 LLM 调用 / 训练。
"""
self._db_path = db_path
self._lock = threading.Lock()
self._conn: sqlite3.Connection | None = None
try:
Path(db_path).parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(str(db_path), check_same_thread=False)
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA busy_timeout=5000")
conn.execute(self._CREATE_TABLE_SQL)
conn.commit()
self._conn = conn
except sqlite3.Error as exc:
logger.warning("遥测连接初始化失败(已降级,后续写入丢弃): {}", exc)
self._conn = None
def _write(
self,
@@ -87,20 +99,19 @@ class SQLiteTelemetryRecorder:
cache_hit: bool,
error: str | None,
) -> None:
"""同步写入一条 LLM 调用记录到 SQLite
"""同步写入一条 LLM 调用记录(单持久连接 + Lock 串行化,对齐 HarnessLog
三层防御加固
1. INSERT OR IGNORE — 主键冲突静默忽略
2. WAL + busy_timeout — 并发写锁容忍
3. try/except sqlite3.Error — DB 错误不冒泡调用
三层防御:
1. INSERT OR IGNORE — call_id 主键冲突静默忽略(幂等)
2. 进程内 threading.Lock 串行化写 — 消除并发锁竞争(非依赖 SQLite busy_timeout
3. try/except sqlite3.Error — DB 错误降级不冒泡,遥测失败绝不拖垮 LLM 调用
"""
if self._conn is None:
logger.warning("遥测连接不可用(已降级),丢弃 call_id={}", call_id)
return
try:
conn = sqlite3.connect(str(self._db_path), timeout=10.0)
try:
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA busy_timeout=5000")
self._ensure_table(conn)
conn.execute(
with self._lock:
self._conn.execute(
self._INSERT_SQL,
(
call_id,
@@ -120,9 +131,7 @@ class SQLiteTelemetryRecorder:
error,
),
)
conn.commit()
finally:
conn.close()
self._conn.commit()
except sqlite3.Error as exc:
logger.warning("遥测写入失败(已降级),call_id={}: {}", call_id, exc)