store: migrations under BEGIN IMMEDIATE, the version read again inside
test / test (push) Canceled after 0s

D-S3. A deferred BEGIN starts nothing until the first access (SQLite,
lang_transaction.html § 2.2), and migrate() read user_version outside the
transaction: after an upgrade the panel and dmarc-ingest, opening the file
at once, could both read the old version, both begin, and the second
applied the next migration again on the migrated file - "already exists",
that Open failing (nothing damaged: its transaction rolled back). The DSN
now has _txlock=immediate (modernc.org/sqlite v1.60.1, sqlite.go
applyQueryParams() sets beginMode, tx.go newTx() issues "begin immediate"
for any transaction not read-only), so a migration holds the write lock
from its BEGIN, and migrate() reads user_version again inside it and
skips one another process applied meanwhile.

Test TestTwoOpensMigrateOnce: a raw connection holds BEGIN IMMEDIATE on a
new file while two Opens read version 0 and queue on their first BEGIN;
released, both succeed and user_version is the number of migrations.
Without the fix it fails as the register said: "apply migration
0001_init.sql: SQL logic error: table settings already exists (1)".
Stand (2026-10-15): go vet and go test ./... pass on the server (24
packages); make e2e exit 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-10-15 01:19:18 +03:00
co-authored by Claude Opus 5.5
parent 9ca50d33fd
commit 3c63ecc1ad
5 changed files with 90 additions and 4 deletions
+3 -1
View File
@@ -999,10 +999,12 @@ those after it move up one patch number each.
app's browser): a `no-store` page is not restored from the back/forward
cache (pageshow persisted=false, a fresh load); the `pagehide` handler is
for browsers that do restore one. Firefox and Safari not measured.
- [ ] **Two processes opening an old database** (D-S3): `user_version` is
- [x] **Two processes opening an old database** (D-S3): `user_version` is
read outside the transaction, so the panel and dmarc-ingest can both
run one migration; `_txlock=immediate` and the version re-read inside
it; a test with two concurrent opens behind a held lock. - **Opus**
*Done 2026-10-15:* the test fails without the fix exactly as the
register said ("table settings already exists") and passes with it.
- [ ] **Certificates from certbot** (C-A1, C-Q4, D-guide:258, :291, :722):
certbot's `live/` files are relative links into `archive/`, so mounting
`live/<host>` alone, or linking `certs` to it, leaves Postfix without a