Files
shelfmark/tests/core/test_db_busy_timeout.py
splitsec2 f560b31ee4 fix(db): wait up to 30 s for a busy writer instead of SQLite's 5 s default (#1539)
The users, download history and activity view-state databases open their
connections with SQLite's default busy timeout of 5 seconds. A start-up
migration, the restart reconcile, or any statement that overlaps a
backup or a second process raised "database is locked" after 5 seconds,
and the reconcile finished only part of its work.

One shared `DB_BUSY_TIMEOUT_SECONDS` (30) in `core/utils.py`, used by
all three. The test reads SQLite's `busy_timeout` pragma back from each
connection, so it doesn't need a real lock fight.

I left `UserDB.initialize()` without a guard on purpose. With 30 seconds
the wait covers a backup, and swallowing a schema failure there would
hide a worker that can't run.

Found in a Claude pass over 1.5.0.
2026-10-22 20:21:49 -04:00

28 lines
928 B
Python

"""The app databases wait out a busy writer instead of failing at SQLite's 5 s default."""
import pytest
from shelfmark.core.activity_view_state_service import ActivityViewStateService
from shelfmark.core.download_history_service import DownloadHistoryService
from shelfmark.core.user_db import UserDB
# SQLite's own default is 5000 ms. A start-up migration or a restart reconcile that meets a
# writer for a few seconds should wait, not raise.
_MIN_BUSY_TIMEOUT_MS = 15_000
@pytest.mark.parametrize(
"service_cls",
[UserDB, DownloadHistoryService, ActivityViewStateService],
)
def test_app_database_connections_wait_for_a_busy_writer(tmp_path, service_cls):
service = service_cls(str(tmp_path / "users.db"))
conn = service._connect()
try:
busy_timeout_ms = conn.execute("PRAGMA busy_timeout").fetchone()[0]
finally:
conn.close()
assert busy_timeout_ms >= _MIN_BUSY_TIMEOUT_MS