Make concurrent database writes wait instead of failing (closes #253)
Check / check (pull_request) Skipped

SQLite transactions started deferred, so two that both read and then
wrote could not both proceed, and SQLite refused the second at once with
"database is locked" without waiting. TestCreateUserRaceCondition hit
this now and then.

The database is now opened with _txlock=immediate, so each transaction
takes the write lock when it begins and a second one waits for it, and
with _busy_timeout=5000, so that wait lasts up to 5 seconds. The driver
already defaults to 5000; it is now stated in the code.

Model: opus-5-5
This commit is contained in:
2026-10-01 19:22:33 +00:00
parent 5f9948d7e2
commit e10d4313bc
2 changed files with 9 additions and 2 deletions
+4
View File
@@ -20,6 +20,10 @@ regress.
# Completed Steps
- 2026-10-01: Two database writes at the same moment no longer fail with
"database is locked": each transaction now takes the write lock when it begins
and waits up to 5 seconds for another writer to finish (#253).
- 2026-10-01: Builds attach a BuildKit session, as the docker command line does,
so a base image that is not on the host is pulled instead of the build failing
with "no active sessions" on Docker Engine 27. Container logs, and so the
+5 -2
View File
@@ -137,8 +137,11 @@ func (d *Database) connect(ctx context.Context) error {
return fmt.Errorf("failed to create data directory: %w", err)
}
// Open database with WAL mode and foreign keys
dsn := dbPath + "?_journal_mode=WAL&_foreign_keys=on"
// Open database with WAL mode and foreign keys. Transactions take the
// write lock when they begin and wait up to 5s for another writer,
// instead of failing with "database is locked" when both write.
dsn := dbPath + "?_journal_mode=WAL&_foreign_keys=on" +
"&_txlock=immediate&_busy_timeout=5000"
database, err := sql.Open("sqlite3", dsn)
if err != nil {