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
+1 -1
View File
@@ -42,7 +42,7 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md).
| from-header-binding | Hold a message's `From:` header against the application that sends it | shipped `2.4.0` | 9/9 | [plans/from-header-binding.md](plans/from-header-binding.md) |
| panel-redesign | Panel redesign (accepted mockups, design contract) | shipped `2.0.0` | 27/27 | [plans/panel-redesign.md](plans/panel-redesign.md) |
| schema-squash | Squash SQLite migrations into a 2.x baseline | built in panel-redesign (stage 1) | — | [plans/panel-redesign.md](plans/panel-redesign.md) |
| audit-2026-10-12 | Follow-ups of the 2026-10-12 full-tree code audit, seven stages | in progress (stage 0 shipped `2.4.2`, stages 1+2 `2.5.0`, 2b `2.5.1`; next stage 2c → `2.5.2`) | 42/81 | [audit-2026-10-12.md](audit-2026-10-12.md) |
| audit-2026-10-12 | Follow-ups of the 2026-10-12 full-tree code audit, seven stages | in progress (stage 0 shipped `2.4.2`, stages 1+2 `2.5.0`, 2b `2.5.1`; next stage 2c → `2.5.2`) | 43/81 | [audit-2026-10-12.md](audit-2026-10-12.md) |
**Recommended order** (not binding; owner, 2026-10-09: feature order follows
development convenience): **panel-redesign** shipped as `2.0.0` (2026-10-11) and