Files
roboco/tests/unit/db/test_engine_server_settings.py
T

79 lines
2.7 KiB
Python
Raw Normal View History

"""Engine-level Postgres session timeouts (2026-07-29 pool-exhaustion class).
A session parked mid-transaction on non-DB work (a git subprocess, an
asyncio lock queue) holds its row locks and pooled connection until Postgres
kills it; a statement queued on someone else's row lock must give up instead
of camping on a pool slot. ``get_engine`` passes both as asyncpg
``server_settings`` so every app connection carries them; a 0 value drops
the setting entirely (Postgres-default behavior, operator off-switch).
"""
from __future__ import annotations
from typing import Any
from unittest.mock import MagicMock
import pytest
from roboco.db import base as db_base
@pytest.fixture
def _holder_reset(monkeypatch: pytest.MonkeyPatch) -> Any:
"""Isolate _DbHolder so the test never touches the real engine."""
monkeypatch.setattr(db_base._DbHolder, "engine", None)
monkeypatch.setattr(db_base._DbHolder, "session_factory", None)
monkeypatch.setattr(db_base._DbHolder, "loop", None)
return db_base._DbHolder
def _capture_engine_kwargs(monkeypatch: pytest.MonkeyPatch) -> dict[str, Any]:
captured: dict[str, Any] = {}
def _fake_create(_url: str, **kwargs: Any) -> MagicMock:
captured.update(kwargs)
return MagicMock()
monkeypatch.setattr(db_base, "create_async_engine", _fake_create)
return captured
def test_engine_carries_server_side_timeouts(
monkeypatch: pytest.MonkeyPatch, _holder_reset: Any
) -> None:
monkeypatch.setattr(
db_base.settings, "database_idle_in_transaction_timeout_ms", 120_000
)
monkeypatch.setattr(db_base.settings, "database_lock_timeout_ms", 30_000)
captured = _capture_engine_kwargs(monkeypatch)
db_base.get_engine()
assert captured["connect_args"]["server_settings"] == {
"idle_in_transaction_session_timeout": "120000",
"lock_timeout": "30000",
}
def test_zero_disables_each_timeout_individually(
monkeypatch: pytest.MonkeyPatch, _holder_reset: Any
) -> None:
monkeypatch.setattr(db_base.settings, "database_idle_in_transaction_timeout_ms", 0)
monkeypatch.setattr(db_base.settings, "database_lock_timeout_ms", 5_000)
captured = _capture_engine_kwargs(monkeypatch)
db_base.get_engine()
assert captured["connect_args"]["server_settings"] == {"lock_timeout": "5000"}
def test_both_zero_sends_no_server_settings(
monkeypatch: pytest.MonkeyPatch, _holder_reset: Any
) -> None:
monkeypatch.setattr(db_base.settings, "database_idle_in_transaction_timeout_ms", 0)
monkeypatch.setattr(db_base.settings, "database_lock_timeout_ms", 0)
captured = _capture_engine_kwargs(monkeypatch)
db_base.get_engine()
assert captured["connect_args"] == {}