Skip to content

Add a Transaction view that scopes reads and writes to a single ACID transaction - #165

Open
jtnelson wants to merge 3 commits into
developfrom
feature/transactions
Open

Add a Transaction view that scopes reads and writes to a single ACID transaction#165
jtnelson wants to merge 3 commits into
developfrom
feature/transactions

Conversation

@jtnelson

@jtnelson jtnelson commented Aug 6, 2026

Copy link
Copy Markdown
Member

Summary

Runway#transaction starts a Transaction: a DatabaseInterface view whose reads and writes all resolve within a single ACID transaction. It exists so a decision made on loaded data can commit only if that data is still current, which closes the read-then-write race that unguarded saves leave open.

Behavior

  • Reads through the view observe the transaction's isolated snapshot, including its own uncommitted writes, and join its conflict footprint; the commit fails instead of persisting a decision that was made on data a concurrent writer changed.
  • Records loaded through the view, and the linked records reachable from them, are bound to it: record.save() stages within the transaction, and the writes become durable only when commit() succeeds. New records join through Transaction#save.
  • An Audience loaded through the view routes the operations it performs through the transaction, so access-controlled reads and writes stay within the snapshot and join the conflict footprint.
  • Save and delete notifications dispatch only after a successful commit. An abort discards every staged write; a record's in-memory edits remain, the same as after a failed save.
  • Transaction is AutoCloseable (close() aborts whatever was not committed) and is confined to the thread that starts it. After it ends, the view forwards reads and saves to the enclosing Runway, so bound records unwind to the database scope; only another commit() is refused.
  • runway.run(work) executes work within a transaction and commits after it completes: the work reads through the provided DatabaseInterface, every record.save() during the work joins the transaction, new records included, and conflicts retry within the bounds of the governing AtomicRetryPolicy, so work must be free of side effects outside the transaction.
  • Single-key atomic operations require a direct Runway binding and are refused on a transaction-bound record; the transaction's commit is the unit of atomicity.
  • A DeferredReference that is first accessed within a transaction resolves within it: the lazy load joins the snapshot and the conflict footprint, and the loaded record binds to the transaction.

Scope

  • A Record binds to a single persistence-capable database view that reads and saves route through, and draws connections through the new ConcourseProvider seam; Record never starts or commits a transaction itself.

@jtnelson
jtnelson force-pushed the feature/transactions branch from 17028f8 to b189545 Compare August 6, 2026 22:10
@jtnelson
jtnelson changed the base branch from feature/GH-163 to develop August 6, 2026 22:10
@jtnelson
jtnelson force-pushed the feature/transactions branch from b189545 to f7ca0ad Compare August 6, 2026 22:55
…action

Runway#transaction starts a Transaction: a DatabaseInterface view whose
reads join the transaction's conflict footprint and whose bound Records
save within it, so a commit fails instead of persisting a decision that
was made on data a concurrent writer changed. An Audience loaded through
the view routes its operations through the transaction, and an ended
transaction forwards to the enclosing Runway so bound records unwind to
the database scope.

A Record now binds to a single PersistentDatabaseInterface and draws
connections through a ConcourseProvider; it never starts or commits a
transaction itself.
@jtnelson
jtnelson force-pushed the feature/transactions branch from f7ca0ad to 90b204a Compare August 6, 2026 23:04
A DeferredReference now follows its owner's binding at the moment of the
first access, so an access inside an open Transaction loads within the
snapshot, joins the conflict footprint, and binds the loaded Record to
the transaction; an access after the transaction ends falls through to
the enclosing Runway.
…uting

Runway#run executes a Consumer of DatabaseInterface within a
transaction: the work reads through the provided view, and every
record.save() during the work, including a save of a brand-new Record,
routes into the ambient transaction, so arbitrary work commits together
or not at all.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant