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. If the mode field is absent from the request, the transaction is 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.

Because the connection has both the hive database and the volatile database attached, a single SQLite transaction spans both. Persistent and volatile mutations made inside one transaction commit together and roll back together, with no separate mechanism reconciling them.

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.

The snapshot is exact for persistent data. Volatile data has no snapshot mechanism: a volatile read inside a read-only transaction observes the live store.

A mutating operation carrying a read-only transaction id is rejected with RSI_INVALID before any state changes — for the nine operations that route through the write path. RSI_DELETE_LAYER and RSI_FLUSH do not check (§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. Rollback errors are logged and not reported. This is how the kernel releases a read-only snapshot after a REG_IOC_BACKUP finishes or fails.

A transaction that is neither committed nor aborted is never cleaned up: there is no timeout and no reaper. It holds its hive's write connection until the process exits — see §4.4.

Edit this page