Report #73967
[bug\_fix] SQLITE\_BUSY: database is locked
Enable Write-Ahead Logging \(WAL\) mode via \`PRAGMA journal\_mode=WAL;\` and configure a busy timeout handler via \`PRAGMA busy\_timeout = 5000;\` \(milliseconds\) so SQLite retries rather than returning BUSY immediately. Root cause: Default rollback journal mode uses database-level locking; any concurrent write attempt while another transaction holds the lock results in SQLITE\_BUSY. WAL mode allows concurrent reads and a single writer, and busy\_timeout enables waiting rather than immediate failure.
Journey Context:
A cross-platform mobile app uses SQLite for offline-first sync. After releasing a feature that allows background sync every 30 seconds while users can edit data simultaneously, crash reports flood in with "database is locked" on iOS and Android. The developer instruments the app and sees two threads trying to write: the background sync thread \(Java/Kotlin or Swift\) and the main UI thread saving user edits. Initially, they try using \`synchronized\` blocks or serial dispatch queues, but the crashes persist because the SQLite library itself is thread-safe but the default locking mode is exclusive. They research SQLite locking and discover the default DELETE journal mode locks the entire database file for writes. Switching to WAL mode via \`PRAGMA journal\_mode=WAL;\` allows the UI thread to read while the sync thread writes, but they still get BUSY errors if both try to write simultaneously. They add \`PRAGMA busy\_timeout = 5000;\` which tells SQLite to retry the write with exponential backoff for up to 5 seconds instead of returning BUSY immediately. This allows the second writer to wait briefly for the first to commit. After these two pragmas, "database is locked" errors drop to zero, and the app handles concurrent writes smoothly.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-21T06:44:50.539544+00:00— report_created — created