fix: SQLite busy_timeout per-connection via DSN (stop SQLITE_BUSY recurrence)#14
Merged
Merged
Conversation
…recurrence busy_timeout is a per-connection setting, but OpenSQLite applied it with a single db.Exec after Open — only the one pooled connection that served the Exec got it. journal_mode=WAL persists at the file level (so WAL engaged via the 0.8.0 fix), but every other pool connection database/sql opened for concurrent work defaulted to busy_timeout=0 and returned SQLITE_BUSY instantly under writer contention (file-watcher index racing a write). Pass the pragmas through the DSN _pragma parameter instead: modernc.org/sqlite runs each one as PRAGMA ... on every new pool connection (Driver.Open). Add _txlock=immediate so writers take the write lock at BEGIN — busy_timeout is not honored when a deferred transaction fails to upgrade a read lock to a write lock (SQLite returns SQLITE_BUSY immediately without invoking the busy handler). The previous ?_journal_mode=WAL DSN form was a non-existent modernc parameter (the driver only honors _pragma=...), which is why the rollback journal showed up on disk in the original incident. Regression test asserts busy_timeout holds on a second pooled connection.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Проблема
index_documentsпериодически падал сdatabase is locked (5) (SQLITE_BUSY), несмотря на фикс инцидента 2026-05-05 (T59,internal/dbutil). Логи: 2026-05-22 14:54delete chunks, 2026-05-23 00:24upsert chunk(trigger: file_watcher).Корень
dbutil.OpenSQLiteставил pragma черезdb.Execпосле Open.journal_mode=WALперсистентен на уровне файла БД (поэтому WAL и заработал), ноbusy_timeout— per-connection: одинExecконфигурирует только то соединение пула, что его обслужило. Остальные соединения, которыеdatabase/sqlоткрывает под конкурентную работу, получалиbusy_timeout=0→ мгновенныйSQLITE_BUSYпри writer-контеншене (фоновый file-watcher индекс против записи).Доп.: исходный
?_journal_mode=WAL— несуществующий параметрmodernc.org/sqlite(драйвер знает только_pragma=...), отсюда rollback-journal на диске в первом инциденте.Фикс
OpenSQLiteпередаёт pragma через DSN:_pragma=→moderncвыполняетPRAGMA ...на каждом новом соединении пула (Driver.Open),busy_timeoutдоходит до всех._txlock=immediate→ writer берёт write-lock наBEGIN.busy_timeoutне вызывается, когда deferred-транзакция апгрейдит read→write lock при занятом writer'е (SQLite сразу возвращаетSQLITE_BUSY, минуя busy handler);BEGIN IMMEDIATEэто закрывает.Регресс-тест
TestOpenSQLite_BusyTimeoutPerConnectionпроверяет timeout на втором соединении пула.Проверено
go build ./...,go vet,go test ./...— всё зелёное.Follow-ups (в
06-planning/2026-05-05-sqlite-busy-incident.md§7)callIndexDocumentsзовётIndexDocumentsмимо guardre.indexing, которым пользуются startup и file_watcher → возможна параллельная запись. RC4-фикс делает их вежливой очередью (ждут до 5с), но при write-txn >5с BUSY теоретически остаётся. Нужен единый writer-guard (меняет семантику) — отдельная задача.:8090) — не связано с БД, отдельно.