diff --git a/backend/database/session.py b/backend/database/session.py index f67864f1..cb051ec2 100644 --- a/backend/database/session.py +++ b/backend/database/session.py @@ -1,10 +1,9 @@ """Engine creation, initialization, and session management.""" import logging -import sqlite3 as _sqlite3 import uuid -from sqlalchemy import create_engine +from sqlalchemy import create_engine, event from sqlalchemy.orm import sessionmaker from .. import config @@ -23,21 +22,6 @@ from .seed import backfill_generation_versions, seed_builtin_presets logger = logging.getLogger(__name__) -def _make_connection(db_path: str) -> _sqlite3.Connection: - """Open a SQLite connection with WAL journal mode and a 5-second busy timeout. - - WAL allows concurrent readers while a write is in progress (the default - DELETE journal blocks all readers). This matters for voicebox because SSE - status polls and history queries run concurrently with the generation worker - writing to the same database. The busy timeout prevents "database is - locked" errors when two writers briefly contend on the same write slot. - """ - conn = _sqlite3.connect(db_path, check_same_thread=False) - conn.execute("PRAGMA journal_mode=WAL") - conn.execute("PRAGMA busy_timeout=5000") - return conn - - # Initialized by init_db() engine = None SessionLocal = None @@ -54,15 +38,21 @@ def init_db() -> None: engine = create_engine( f"sqlite:///{_db_path}", connect_args={"check_same_thread": False}, - # Each connection enables WAL journal mode and sets a 5-second busy - # timeout. WAL allows concurrent readers during a write (the default - # DELETE/ROLLBACK journal blocks all readers), which matters for - # voicebox because SSE status polls and history queries run + ) + + @event.listens_for(engine, "connect") + def _set_sqlite_pragmas(dbapi_connection, _record) -> None: + # Each pooled connection enables WAL journal mode and sets a 5-second + # busy timeout. WAL allows concurrent readers during a write (the + # default DELETE/ROLLBACK journal blocks all readers), which matters + # for voicebox because SSE status polls and history queries run # concurrently with the generation worker writing to the same db. # busy_timeout prevents "database is locked" errors when two # connections briefly contend on the same write slot. - creator=lambda: _make_connection(str(_db_path)), - ) + cursor = dbapi_connection.cursor() + cursor.execute("PRAGMA journal_mode=WAL") + cursor.execute("PRAGMA busy_timeout=5000") + cursor.close() SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/docs/content/docs/overview/troubleshooting.mdx b/docs/content/docs/overview/troubleshooting.mdx index f615b3c6..7668b873 100644 --- a/docs/content/docs/overview/troubleshooting.mdx +++ b/docs/content/docs/overview/troubleshooting.mdx @@ -419,12 +419,14 @@ bun run tauri build ```bash # macOS -rm ~/Library/Application\ Support/sh.voicebox.app/data/voicebox.db +rm ~/Library/Application\ Support/sh.voicebox.app/data/voicebox.db* # Windows -del %APPDATA%\sh.voicebox.app\data\voicebox.db +del %APPDATA%\sh.voicebox.app\data\voicebox.db* ``` +The wildcard also removes the `voicebox.db-wal` and `voicebox.db-shm` sidecar files that SQLite keeps next to the database in WAL mode, so the reset starts completely clean. + Restart the app to create a fresh database. ## Model Issues diff --git a/justfile b/justfile index 877f1519..9b9a480f 100644 --- a/justfile +++ b/justfile @@ -348,12 +348,12 @@ db-init: _ensure-venv # Reset database (delete + reinit) [unix] db-reset: - rm -f {{ backend_dir }}/data/voicebox.db + rm -f {{ backend_dir }}/data/voicebox.db {{ backend_dir }}/data/voicebox.db-wal {{ backend_dir }}/data/voicebox.db-shm just db-init [windows] db-reset: - if (Test-Path "{{ backend_dir }}/data/voicebox.db") { Remove-Item -Force "{{ backend_dir }}/data/voicebox.db" } + Remove-Item -Force -ErrorAction SilentlyContinue "{{ backend_dir }}/data/voicebox.db", "{{ backend_dir }}/data/voicebox.db-wal", "{{ backend_dir }}/data/voicebox.db-shm" just db-init # ─── Utilities ────────────────────────────────────────────────────────