Concurrent database writes fail with "database is locked" instead of waiting #253

Closed
opened 2026-10-01 20:52:41 +02:00 by clawbot · 1 comment
Collaborator

TestCreateUserRaceCondition in internal/service/auth/auth_test.go fails now and then on a busy host: one of the ten concurrent CreateUser calls returns "inserting user: database is locked" instead of "user already exists". It failed make check on next at d69f11f; nothing in that change touches the database.

Cause: internal/database/database.go opens SQLite with ?_journal_mode=WAL&_foreign_keys=on, so there is no busy timeout and every transaction starts deferred. CreateFirstUser (internal/models/user.go) reads the user count and then inserts. Two such transactions both read, then both try to write, and SQLite refuses the second at once with "database is locked" instead of making it wait. The same can happen to any two concurrent writers in upaas, not only in the test.

Definition of done:

  • The database is opened so that a write transaction waits for another to finish instead of failing: _txlock=immediate and a busy timeout (_busy_timeout, a few seconds) in the github.com/mattn/go-sqlite3 connection string, or the driver's documented equivalent.
  • TestCreateUserRaceCondition passes reliably: run repeatedly through make test it does not fail, and it fails without the change often enough to show the change is what fixes it.
  • No other test or behaviour changes.

Model: opus-5-5

`TestCreateUserRaceCondition` in `internal/service/auth/auth_test.go` fails now and then on a busy host: one of the ten concurrent `CreateUser` calls returns "inserting user: database is locked" instead of "user already exists". It failed `make check` on `next` at `d69f11f`; nothing in that change touches the database. Cause: `internal/database/database.go` opens SQLite with `?_journal_mode=WAL&_foreign_keys=on`, so there is no busy timeout and every transaction starts deferred. `CreateFirstUser` (`internal/models/user.go`) reads the user count and then inserts. Two such transactions both read, then both try to write, and SQLite refuses the second at once with "database is locked" instead of making it wait. The same can happen to any two concurrent writers in upaas, not only in the test. Definition of done: - The database is opened so that a write transaction waits for another to finish instead of failing: `_txlock=immediate` and a busy timeout (`_busy_timeout`, a few seconds) in the `github.com/mattn/go-sqlite3` connection string, or the driver's documented equivalent. - `TestCreateUserRaceCondition` passes reliably: run repeatedly through `make test` it does not fail, and it fails without the change often enough to show the change is what fixes it. - No other test or behaviour changes. Model: opus-5-5
clawbot self-assigned this 2026-10-01 20:52:42 +02:00
Author
Collaborator

#258 opens the database so each transaction takes the write lock when it begins and a second one waits up to 5 seconds instead of failing. The driver already waited 5 seconds by default; what was missing was taking the write lock at the start.

Model: opus-5-5

https://git.eeqj.de/sneak/upaas/pulls/258 opens the database so each transaction takes the write lock when it begins and a second one waits up to 5 seconds instead of failing. The driver already waited 5 seconds by default; what was missing was taking the write lock at the start. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/upaas#253