diff --git a/TODO.md b/TODO.md index 6385244..cc5f8f3 100644 --- a/TODO.md +++ b/TODO.md @@ -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 diff --git a/internal/database/database.go b/internal/database/database.go index 929ef5e..75cc220 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -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 {