fix: harden assistant SQLite/PostgreSQL dual-backend (singleton, timeouts, warnings)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-05-14 10:24:23 +08:00
parent 1490bd5f24
commit 1caf96e694
8 changed files with 129 additions and 15 deletions

View file

@ -2,13 +2,20 @@
from __future__ import annotations
import hashlib
import threading
from pathlib import Path
from svc.config.database import assistant_db_backend, assistant_postgres_dsn
from svc.config.database import (
assistant_db_backend,
assistant_postgres_dsn,
assistant_sqlalchemy_url,
)
from svc.persistence.assistant_store_protocol import AssistantStoreProtocol
_singleton: AssistantStoreProtocol | None = None
_singleton_key: str | None = None
_lock = threading.Lock()
def reset_assistant_store_singleton() -> None:
@ -29,6 +36,12 @@ def get_assistant_store() -> AssistantStoreProtocol:
does not re-run PostgreSQL bootstrap / orphan pruning on every HTTP or WS call (which could race
with in-flight writes and make messages disappear after tool rounds).
The cache key for PostgreSQL uses a **hash of the SQLAlchemy URL** (not the raw DSN string) so logs
and debug dumps are less likely to embed credentials.
When the effective key changes (e.g. tests switch ``AIA_ASSISTANT_DB_PATH`` or DSN), the SQLAlchemy
engine cache is cleared so pooled connections are not reused for the wrong database.
Tests should keep constructing ``SqliteStore(path)`` with an explicit file path; production code
should prefer this factory for ``db_path()``-backed instances.
@ -40,18 +53,27 @@ def get_assistant_store() -> AssistantStoreProtocol:
from svc.persistence.sqlite_store import SqliteStore
if assistant_db_backend() == "postgresql":
key = f"postgresql::{assistant_postgres_dsn()}"
key = f"postgresql::{hashlib.sha256(assistant_sqlalchemy_url().encode()).hexdigest()}"
else:
key = f"sqlite::{Path(db_path()).resolve()}"
if _singleton is not None and _singleton_key == key:
inst = _singleton
if inst is not None and _singleton_key == key:
return inst
with _lock:
if _singleton is not None and _singleton_key == key:
return _singleton
if _singleton_key is not None and _singleton_key != key:
from svc.persistence.db.engine import clear_assistant_engine_cache
clear_assistant_engine_cache()
if assistant_db_backend() == "postgresql":
_singleton = SqliteStore(None, postgres_url=assistant_postgres_dsn())
else:
_singleton = SqliteStore(db_path())
_singleton_key = key
return _singleton
if assistant_db_backend() == "postgresql":
_singleton = SqliteStore(None, postgres_url=assistant_postgres_dsn())
else:
_singleton = SqliteStore(db_path())
_singleton_key = key
return _singleton
__all__ = ["get_assistant_store", "reset_assistant_store_singleton"]