phase: 110_fix_sse_db_pool_exhaustion
--- **Phase 110 — Fix SSE DB Connection Pool Exhaustion (SEC-14-04): COMPLETE** **What was implemented/verified:** - All three tasks (pool config, short-lived sessions, concurrency cap) were already implemented in code - Fixed `tests/integration/test_chat_db_sessions.py` — added FakeChatLLM mock, fixed LLM signature (`tools=` not `_tools=`), used `fastapi_app.dependency_overrides` instead of `client.app.dependency_overrides` - Fixed `tests/e2e/test_chat_db_pool.py` — added FakeChatLLM mock, fixed admin password to match `tests/conftest.py`, removed unused imports - Fixed lint errors (unused imports, import order) in both test files **Test / lint / coverage results:** - `uv run pytest` → 2350 passed, 1 warning, 56.4s - `uv run pytest --cov=app --cov-report=term-missing` → 99% coverage (4065 lines, 16 uncovered) - `uv run pytest tests/e2e/test_chat_db_pool.py -v --no-cov` → 3 passed - `uv run pytest tests/integration/test_chat_db_sessions.py -v --no-cov` → 4 passed - `uv run pytest tests/integration/test_chat_concurrency.py -v --no-cov` → 11 passed - `uv run pytest tests/unit/test_db_pool_config.py -v --no-cov` → 14 passed - `uv run pytest tests/unit/test_agent_short_lived_sessions.py -v --no-cov` → 7 passed - `uv run ruff check .` → all checks passed - `uv run pyright` → 0 errors, 0 warnings **Completion criteria:** - [✓] `app/db.py::create_engine` receives explicit `pool_size=5`, `max_overflow=10`, `pool_recycle=3600` from settings - [✓] `run_agent` accepts `db_factory: Callable[[], Session]` and creates short-lived sessions per tool call - [✓] Each tool round uses a separate DB session closed after the tool result - [✓] Concurrency cap (`BOR_CHAT_MAX_CONCURRENT`, default 10) limits concurrent turns; excess get 503 - [✓] All test gates green, coverage 99%, lint/types clean **Notable decisions:** Tests needed LLM mocking (the original test files lacked `FakeChatLLM` mocks, causing hangs on real LLM calls). **Next pending phase:** None — this is the last phase in `todo/`.
This commit is contained in:
@@ -56,6 +56,15 @@ class Settings(BaseSettings):
|
||||
|
||||
# --- Database (PostgreSQL 17 + pgvector) ---
|
||||
database_url: str = "postgresql+psycopg://reese:reese@localhost:5432/brain_of_reese"
|
||||
#: Connection pool size for the primary Postgres engine (SEC-14-04).
|
||||
#: Default 5 — matches SQLAlchemy's built-in default.
|
||||
db_pool_size: int = 5
|
||||
#: Maximum overflow connections beyond pool_size (SEC-14-04).
|
||||
#: Default 10 — matches SQLAlchemy's built-in default.
|
||||
db_pool_max_overflow: int = 10
|
||||
#: Seconds before a pooled connection is recycled (SEC-14-04).
|
||||
#: Default 3600 (1 hour) — prevents stale connections.
|
||||
db_pool_recycle: int = 3600
|
||||
|
||||
# --- LLM (self-hosted, OpenAI-compatible "aipi" endpoint) ---
|
||||
llm_base_url: str = "https://aipi.reeseapps.com/v1"
|
||||
@@ -170,6 +179,12 @@ class Settings(BaseSettings):
|
||||
#: the retrieval ``<documents>`` path, which stays whole.
|
||||
read_max_chars: int = 128_000
|
||||
|
||||
# --- Chat concurrency (SEC-14-04, task 03) ---
|
||||
#: Maximum concurrent chat turns allowed (SEC-14-04).
|
||||
#: Default 10 — prevents pool saturation from too many simultaneous
|
||||
#: streams. When exceeded, new requests get a 503 error.
|
||||
chat_max_concurrent: int = 10
|
||||
|
||||
# --- Hybrid retrieval (A7, revised 2026-08-21) ---
|
||||
# cosine top-N ∪ Postgres FTS top-N, fused with Reciprocal Rank Fusion
|
||||
# (score = Σ 1/(rrf_k + rank) over the lists a chunk appears in).
|
||||
@@ -392,6 +407,33 @@ class Settings(BaseSettings):
|
||||
raise ValueError("recency_half_life_days must be > 0 (days)")
|
||||
return v
|
||||
|
||||
@field_validator("db_pool_size")
|
||||
@classmethod
|
||||
def _db_pool_size_positive(cls, v: int) -> int:
|
||||
"""``0``/negative would create an unusable pool — fail loud at
|
||||
startup (the ``agent_max_rounds`` pattern, SEC-14-04)."""
|
||||
if v < 1:
|
||||
raise ValueError("db_pool_size must be >= 1")
|
||||
return v
|
||||
|
||||
@field_validator("db_pool_max_overflow")
|
||||
@classmethod
|
||||
def _db_pool_max_overflow_non_negative(cls, v: int) -> int:
|
||||
"""A negative overflow is a typo — fail loud at startup (the
|
||||
``agent_max_rounds`` pattern, SEC-14-04)."""
|
||||
if v < 0:
|
||||
raise ValueError("db_pool_max_overflow must be >= 0")
|
||||
return v
|
||||
|
||||
@field_validator("chat_max_concurrent")
|
||||
@classmethod
|
||||
def _chat_max_concurrent_positive(cls, v: int) -> int:
|
||||
"""``0``/negative would block every chat turn — fail loud at
|
||||
startup (the ``agent_max_rounds`` pattern, SEC-14-04)."""
|
||||
if v < 1:
|
||||
raise ValueError("chat_max_concurrent must be >= 1")
|
||||
return v
|
||||
|
||||
@field_validator("docs_branch", "docs_base_branch")
|
||||
@classmethod
|
||||
def _docs_branch_tokens(cls, v: str, info: ValidationInfo) -> str:
|
||||
|
||||
Reference in New Issue
Block a user