mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
fix(op-log): serialize SQLite adapter transactions on the shared connection (#8849)
* fix(op-log): serialize SQLite adapter transactions on shared connection The SQLite op-log adapter issued raw BEGIN/COMMIT on the shared connection with no serialization. Once both stores share one SqliteDb (the staged native rollout), concurrent operations (capture append, archive write, compaction) would interleave BEGINs: SQLite has no nested transactions, so a second BEGIN throws and a bare statement issued mid-transaction silently joins — and rolls back with — the foreign transaction, corrupting op-log state. Funnel every adapter entry point through an internal FIFO queue so a transaction is exclusive on the connection for its whole BEGIN/COMMIT and no bare operation interleaves. Transaction-internal work runs directly on the connection (already holding the slot), so there is no re-entrancy. Document the mutual-exclusion invariant in the port contract and add a concurrent-transactions contract test that runs on both the in-memory fake and real sql.js. * docs: add complete architecture review report (2026-07-07) Whole-app architecture review synthesized from eight parallel subsystem reviews and an adversarial verification pass; findings filed as issues #8832-#8843, with duplicates cross-referenced rather than re-filed. * fix(op-log): key the SQLite transaction serializer to the connection Multi-agent review found the serializer was keyed to the adapter instance. The native rollout hands the op-log store and archive store two separate SqliteOpLogAdapter instances over one shared SqliteDb, so per-instance queues left an op-log BEGIN free to interleave with a concurrent archive BEGIN on the shared connection — the exact hazard the serializer targets. Key the FIFO queue to the connection (WeakMap<SqliteDb>) so every adapter over one SqliteDb shares one queue. Add a contract test that drives two adapters over one connection concurrently (verified red with per-instance keying, green with per-connection). Also: document the re-entrancy precondition as unenforced (a lint rule, not a runtime flag, is the right guard — a flag cannot distinguish a re-entrant call from a legal concurrent one) and correct init/getLastSeq/port-contract doc drift.
This commit is contained in:
parent
0d7e011081
commit
05f6bd27d1
6 changed files with 466 additions and 37 deletions
|
|
@ -157,7 +157,24 @@ captured on the next tick.
|
|||
**off**. The factory must hand **both** services' adapters the **same**
|
||||
`SqliteDb` (one SQLite file, all tables) — mirroring how they share one IDB
|
||||
connection today. Needs B1 (the native `SqliteDb` wrapper) first.
|
||||
- **Size:** tiny token flip (init change done). **Risk:** gated by the flag.
|
||||
- ✅ **Shared-connection safety (prerequisite for the flip, landed).** With both
|
||||
services on one `SqliteDb`, concurrent op-log operations (capture append vs.
|
||||
archive write vs. compaction) would otherwise interleave `BEGIN`s on the one
|
||||
connection — SQLite has no nested transactions, so a second `BEGIN` throws and
|
||||
a bare statement issued mid-transaction joins (and rolls back with) the foreign
|
||||
transaction. `SqliteOpLogAdapter` now funnels every entry point through a FIFO
|
||||
queue keyed to the **shared connection** (`WeakMap<SqliteDb, …>`, not the
|
||||
adapter instance) — so the op-log store's and archive store's separate adapters
|
||||
over the one `SqliteDb` serialize against **each other**, and a transaction is
|
||||
exclusive on the connection for its whole `BEGIN…COMMIT`. The port contract
|
||||
documents the invariant; contract tests cover concurrent transactions on both
|
||||
engines **and** the two-adapters-one-connection topology. (Closes the H-6/#8746
|
||||
rollout blocker.) Residual: the re-entrancy precondition (a `transaction()`
|
||||
callback must use its `tx` handle, never re-enter an adapter method, or it
|
||||
deadlocks on the queue) is documented but unenforced — a lint rule is the right
|
||||
future guard (a runtime flag can't tell a re-entrant call from a legal
|
||||
concurrent one).
|
||||
- **Size:** tiny token flip (init + serialization done). **Risk:** gated by the flag.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue