Report #50092
[bug\_fix] attempt to write a readonly database
The root cause is SQLite's file-based locking model conflicting with concurrent write operations. By default, SQLite uses the 'DELETE' journal mode which requires an exclusive lock on the entire database file during commits. When multiple threads or processes attempt to write simultaneously \(or when a reader exists during a write\), the database file becomes locked. On Windows, file locking is mandatory \(not just advisory\), making this worse. The fix requires enabling Write-Ahead Logging \(WAL\) mode and implementing connection timeouts: \(1\) Execute 'PRAGMA journal\_mode=WAL;' on the database connection immediately after opening it. WAL mode allows readers and writers to coexist; writers append to the WAL file without locking the main database, and readers see a consistent snapshot. This is the only robust way to handle concurrent access in SQLite. \(2\) Set a busy timeout to handle transient locks: 'PRAGMA busy\_timeout = 5000;' \(milliseconds\). This makes SQLite retry the operation for 5 seconds before returning 'database is locked'. \(3\) Ensure all connections share the same underlying database file path and WAL mode is persisted \(it's a persistent pragma\). Without WAL, no amount of timeout tuning will fix concurrency issues under write load.
Journey Context:
Your Flask app works fine on macOS, but on Windows Docker Desktop, users report 'sqlite3.OperationalError: attempt to write a readonly database' under concurrent load. You check the code and see multiple threads are writing to the same SQLite file. You check the database URI and it's just 'sqlite:///app.db'. You realize SQLite on Windows has different file locking behavior \(mandatory vs advisory\) and the default SQLite journal\_mode is DELETE, which requires exclusive locks during commits. The rabbit hole: you try increasing the timeout, but it just delays the error. You realize WAL mode isn't enabled, and the Docker volume mount on Windows involves SMB/CIFS translation which complicates locking further.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-19T14:33:43.511149+00:00— report_created — created