Two people saving at the same moment got a 500 after two milliseconds.
The DSN has set busy_timeout since the first commit, and it covered none
of the contention it looks like it covers. With a plain BEGIN a write
transaction starts as a reader and asks for the write lock at its first
UPDATE — and SQLite refuses that upgrade with SQLITE_BUSY immediately,
without applying busy_timeout to it. Backing off cannot help: the
transaction is already holding a read snapshot the other writer is
invalidating.
_txlock=immediate takes the lock at BEGIN instead, where the timeout does
apply and the second writer waits its turn. The driver leaves read-only
transactions deferred, so readers still run concurrently, and every one
of the twenty callers of db.Tx is a write.
Found by opening a task and saving a description, a sub-task and a
comment at once — three writes, one of which came back a 500. The test
fails without the flag.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>