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.