SQLite 'database is locked' when manual fetch overlaps the scheduler #97

Closed
opened 2026-08-14 08:05:42 +00:00 by agent-pi · 1 comment
Collaborator

Running a manual "Fetch All Active Streams" while the scheduler's fetch job is active produces sqlite3.OperationalError: database is locked in the scheduler (_fetch_stream_job). The scheduler flushes saved posts (acquiring SQLite's single write lock) and then holds it through the slow inline AI classification (image download + vision, tens of seconds) until commit. Concurrent manual fetches write to the same SQLite file, and the engine config has no busy timeout (connect_args only sets check_same_thread=False), so the loser errors immediately instead of waiting.

Running a manual "Fetch All Active Streams" while the scheduler's fetch job is active produces `sqlite3.OperationalError: database is locked` in the scheduler (`_fetch_stream_job`). The scheduler flushes saved posts (acquiring SQLite's single write lock) and then holds it through the slow inline AI classification (image download + vision, tens of seconds) until commit. Concurrent manual fetches write to the same SQLite file, and the engine config has no busy timeout (`connect_args` only sets `check_same_thread=False`), so the loser errors immediately instead of waiting.
Author
Collaborator

Findings: SQLite's busy timeout is effectively zero here (the engine's connect_args sets only check_same_thread=False), so a concurrent writer errors immediately. And the fetch flow holds the write lock across the slow inline classification (image download + vision, tens of seconds).

Plan:

  1. database.py: add a SQLite connect event listener running PRAGMA busy_timeout = 30000 on each new connection, so a writer waits up to 30s instead of erroring.
  2. Restructure the three fetch spots (scheduler._fetch_stream_job, fetch_hashtag_stream, liveview _fetch_all_active_streams) to db.commit() the saved posts + deterministic auto-reject before classify_posts, releasing the write lock during the slow AI calls, then a second commit for the classification + AI auto-reject.
  3. TDD: unit test for the PRAGMA helper, plus a test that saved posts survive a classification failure (proving the early commit).

Branch: feat/issue-97-sqlite-locking, PR into feat/issue-80-collect with Refs #97.

Findings: SQLite's busy timeout is effectively zero here (the engine's `connect_args` sets only `check_same_thread=False`), so a concurrent writer errors immediately. And the fetch flow holds the write lock across the slow inline classification (image download + vision, tens of seconds). Plan: 1. `database.py`: add a SQLite `connect` event listener running `PRAGMA busy_timeout = 30000` on each new connection, so a writer waits up to 30s instead of erroring. 2. Restructure the three fetch spots (`scheduler._fetch_stream_job`, `fetch_hashtag_stream`, liveview `_fetch_all_active_streams`) to `db.commit()` the saved posts + deterministic auto-reject *before* `classify_posts`, releasing the write lock during the slow AI calls, then a second commit for the classification + AI auto-reject. 3. TDD: unit test for the PRAGMA helper, plus a test that saved posts survive a classification failure (proving the early commit). Branch: `feat/issue-97-sqlite-locking`, PR into `feat/issue-80-collect` with `Refs #97`.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marvin8/dujiangyan#97
No description provided.