Standard Transaction Creation (UnitOfWork + CreateTransaction)
Open a
UnitOfWorkonce, draw as many transactions from it as the batch needs.
Status: โ Implemented ยท Visibility: Public ยท Level: ๐ข Start Here ยท Category: Transactions
๐ฏ What it solves
Batched workloads โ a game tick, a request handler processing several operations, a bulk import โ need many
transactions to share one durability decision: how many writes ride behind a single WAL flush. Creating a fresh
durability boundary per transaction would mean a fresh UowId allocation and a separate flush decision per write,
defeating the point of batching. The standard pattern gives the caller one long-lived UnitOfWork and lets it mint
transactions on demand, each independently atomic but sharing the same flush policy.
โ๏ธ How it works (in brief)
dbe.CreateUnitOfWork(mode, timeout) allocates the UnitOfWork โ a UowId from the registry, an absolute
deadline, and (for Deferred/GroupCommit) a shared ChangeSet. The caller then calls uow.CreateTransaction()
as many times as needed; each call pulls a Transaction from the pooled TransactionChain, stamps it with the
UoW's identity, and returns it ready for use. Each transaction still commits or rolls back independently โ the UoW
only controls when the WAL records those commits produced become crash-safe, via Flush()/FlushAsync() or
automatically on Dispose() (mode-dependent).
๐ป Usage
[Component("Game.Position", 1, StorageMode = StorageMode.SingleVersion)]
struct Position { public float X, Y, Z; }
[Archetype]
partial class Unit : Archetype<Unit>
{
public static readonly Comp<Position> Pos = Register<Position>();
}
using var uow = dbe.CreateUnitOfWork(DurabilityMode.GroupCommit, timeout: TimeSpan.FromSeconds(5));
foreach (var move in pendingMoves)
{
using var tx = uow.CreateTransaction();
tx.Spawn<Unit>(Unit.Pos.Set(move.Position));
tx.Commit(); // ~1-2ยตs, visible immediately, durable within the group-commit window
}
await uow.FlushAsync(); // wait for this batch's records to reach stable media
Console.WriteLine(uow.CommittedTransactionCount);
| Option | Default | Effect |
|---|---|---|
durabilityMode |
DurabilityMode.Deferred |
See Durability Modes โ when this UoW's WAL records become crash-safe. |
timeout |
TimeoutOptions.Current.DefaultUowTimeout |
Absolute deadline shared by every transaction, lock, and flush wait created under this UoW. |
discipline (on CreateTransaction) |
DurabilityDiscipline.TickFence |
Per-transaction SingleVersion write discipline โ see Durability Discipline. |
โ ๏ธ Guarantees & limits
- One
UnitOfWorkcan back any number of sequential or concurrent-on-different-thread transactions; there is no cap onCreateTransaction()calls beyond the engine-wide active-transaction limit. - The caller owns the
UnitOfWork's lifetime explicitly โ it is not disposed automatically by any transaction it creates, unlikeCreateQuickTransaction. - UoWs are flat, not nestable โ
CreateUnitOfWork()called while another is "logically" in scope just allocates a second, independent UoW with its ownUowIdand deadline. TransactionCount/CommittedTransactionCounton theUnitOfWorkare observational counters; they do not gateFlush()orDispose().CreateUnitOfWorkblocks (and can throwResourceExhaustedException) if the UoW Registry is full and the deadline expires before a slot frees โ the standard pattern is the only one of the three that can hit this, since it is the only one that allocates a registry slot the caller controls the lifetime of.
๐งช Tests
- UnitOfWorkTests โ
UoW_CreateTransaction_ReturnsValidTx,UoW_MultipleTransactions_ShareIdentity(sharedOwningUnitOfWorkidentity),MultipleUoWs_ConcurrentAccess(independent UoWs read/write concurrently)
๐ Related
- Code:
src/Typhon.Engine/Transactions/public/UnitOfWork.cs,src/Typhon.Engine/Ecs/public/DatabaseEngine.cs(CreateUnitOfWork) - Related features: Unit of Work, Durability Modes
- Parent feature: Transaction Creation Patterns