4.3 Transactions

Transaction identifiers are allocated by the kernel and carried on every request. loregd binds them to connections lazily — RSI_BEGIN_TRANSACTION does no SQLite work at all.

4.3.1 Beginning #

RSI_BEGIN_TRANSACTION carries the transaction id and a mode: RSI_TXN_READ_WRITE (0) or RSI_TXN_READ_ONLY (1). loregd records the id as pending in the requested mode and returns RSI_OK immediately. No connection is taken and no SQLite transaction is opened.

loregd supports both modes — its SQLite backing provides atomic read-write commits and stable read-only snapshots — so it never returns RSI_TXN_NOT_SUPPORTED.

Re-using a transaction id that is already active returns RSI_INVALID. A missing or truncated mode field, or a mode other than 0 or 1, returns RSI_INVALID without recording a transaction. The id remains available for a subsequent well-formed request. Callers must send the complete u64 id and u32 mode that PSPK §4.4 requires; a request carrying only the id is rejected, not treated as read-write.

4.3.2 Read-write transactions #

The transaction binds to a hive on its first mutating operation: loregd identifies the hive from the operation's GUID, acquires that hive's write connection, issues BEGIN IMMEDIATE, and records the binding. If SQLite reports SQLITE_BUSY at that point, the operation returns RSI_TXN_BUSY.

Once bound, every subsequent operation with that transaction id — reads included — runs on the same connection. That is what provides read-your-own-writes: uncommitted rows are visible to the transaction because it is the connection that wrote them. Reads issued before the transaction binds go to the read pool instead, since there is nothing uncommitted to see.

One SQLite transaction spans both databases. A per-hive publication gate excludes readers while a commit publishes, so readers cannot observe one store before the commit and the other after it. Non-transactional mutations use the same gate for their whole operation. The gate is released between transaction requests, allowing ordinary readers to see committed data while a write transaction remains open.

SQLite WAL does not provide crash-atomic commit across attached database files. After a crash, the persistent hive is recovered by SQLite and the volatile store is discarded, never replayed (§3.3).

An operation whose GUID belongs to a different hive than the transaction is bound to is rejected with RSI_STORAGE_ERROR. The kernel enforces hive-scoping before requests reach loregd, so this is a backstop.

4.3.3 Read-only transactions #

A read-only transaction binds on its first read. loregd identifies the hive, opens a dedicated connection — deliberately not one from the read pool, so a long-lived snapshot cannot starve ordinary reads — and issues BEGIN DEFERRED. WAL fixes the snapshot at that first read, and every later read with the same transaction id reuses the connection and observes the same point in time.

Before the first request proceeds, loregd reads both schemas while holding the hive gate, pinning both WAL snapshots at the same published state. Later reads, including the first access to a volatile key, retain that state. The snapshot does not block volatile writes.

A mutating operation carrying a read-only transaction id is rejected with RSI_INVALID before any state changes. The nine operations that route through the write path get this from the routing itself; RSI_DELETE_LAYER and RSI_FLUSH cannot route that way and ask directly, for the same answer (§4.1).

4.3.4 Committing and aborting #

RSI_COMMIT_TRANSACTION issues COMMIT on the bound connection and returns RSI_OK. If the commit fails, the transaction is left open so the caller may retry or abort: a busy or locked failure returns RSI_TXN_BUSY, anything else RSI_STORAGE_ERROR. Committing an unknown transaction id returns RSI_STORAGE_ERROR.

Committing a read-only transaction releases the snapshot and returns RSI_OK.

RSI_ABORT_TRANSACTION issues ROLLBACK, closes the connection, releases any snapshot, runs the transaction's abort hooks, and always returns RSI_OK — including for a transaction id it has never seen. A connection with an uncertain rollback is discarded rather than returned to the pool. This is how the kernel releases a read-only snapshot after a REG_IOC_BACKUP finishes or fails.

LCS sends aborts when a client transaction expires or its last handle closes. loregd also rolls back every remaining transaction during shutdown. A renewable cleanup lease also reclaims state when an abort is lost. Its authority comes from the live kernel owner, for both read-write transactions and read-only snapshots; it is not an inactivity timeout (§4.4).

If a request interrupts a bound SQLite transaction, loregd marks that ID failed. Reads, writes and commits on it return a storage error until LCS aborts it; it is never silently rebound. This handles SQLite's possible automatic rollback after interruption.

Edit this page