Behaviour that wraps the lifecycle of a single FDB transaction in a process.
dgen_backend:transactional/2 runs a closure and auto-retries it: simple, but
the transaction is anonymous and the caller's process blocks for the whole
commit. dgen_transaction instead gives the transaction its own process.
The owning process creates the transaction, runs a callback module's body
against it, decides when to commit, drives the retry loop, and delivers the
outcome asynchronously. Two things this buys us:
- More control over the transaction lifecycle. For example, a dgen_transaction
can be written using a previous transaction's GRV instead of paying for
a fresh GRV, then falling back to a fresh GRV automatically on retry
(the backend's
on_error/2resets the read version). - Non-blocking commit. A calling process can hand a write to a
dgen_transactionworker and keep receiving messages from its queue while the commit is in flight, learning the result via a message.
Lifecycle
start_link/3 ──▶ init/1 ──▶ create_transaction ──▶ apply read version
│
handle_begin/2
│ │ │
{noreply} {commit} {stop}
│ │ │
(interactive) commit abort
handle_cast/3 │
handle_call/4 ▼
┌── ok ──▶ handle_committed/2 ──▶ {committed, Result}
│
└ retryable error ─▶ handle_conflict/2
retry ─▶ on_error ─▶ handle_retry/2 ─▶ (commit again)
{stop, R} ─▶ {aborted, R}Result delivery
When the transaction reaches a terminal state the worker sends the owner
{dgen_transaction, Ref, Reply}where Ref is the caller-supplied (or generated) correlation token and Reply
is one of:
{committed, Result}—Resultis whateverhandle_committed/2returned.{aborted, Reason}— the module chose not to commit / not to retry.{error, Reason}— a non-retryable backend error, retry-limit exhaustion, or the worker terminating abnormally.
Callbacks
init/1— set up callback state.handle_begin/2— issue the transaction body (reads for fencing + writes) against the liveTx; replayed on every retry unlesshandle_retry/2is provided. Returns a directive (noreply, commit, etc).handle_committed/2— called with the committed version after a successful commit; returns{ok, Result}.
Optional:
handle_cast/3,handle_call/4— interactive operations against the live transaction before commit (the process-model the caller drives directly).handle_retry/2— re-issue the body on retry (defaults tohandle_begin/2).handle_conflict/2— veto or allow a retry on a retryable error (defaults toretry). It governs retryable errors raised either by the commit or while issuing the body (e.g. a read at a stale pinned read version) — both route throughhandle_conflict/2→on_error/2→ replay, so a too-old pinned read falls back to a fresh GRV rather than failing the worker.terminate/2— cleanup; receives the terminalReply.
Summary
Functions
Runs a transaction worker synchronously and returns its Reply.
Like start_link/3 but without linking the worker to the caller.
Starts (and links) a transaction worker for Module.
Like start_link/3 but creates a monitor and does not create a link.
Types
-type cb_state() :: term().
-type opt() :: {db, dgen_backend:db()} | {read_version, undefined | integer()} | {owner, pid()} | {ref, term()} | {max_retries, non_neg_integer() | infinity}.
-type opts() :: [opt()].
Callbacks
-callback handle_begin(Tx :: dgen_backend:tx(), State :: cb_state()) -> directive().
-callback handle_call(Msg :: term(), From :: gen_server:from(), Tx :: dgen_backend:tx(), State :: cb_state()) -> {reply, term(), cb_state()} | directive().
-callback handle_cast(Msg :: term(), Tx :: dgen_backend:tx(), State :: cb_state()) -> directive().
-callback handle_retry(Tx :: dgen_backend:tx(), State :: cb_state()) -> directive().
Functions
Runs a transaction worker synchronously and returns its Reply.
Convenience wrapper around gen_server:start_monitor/3 (unlinked, started and
monitored atomically): blocks until the worker delivers its terminal Reply, or
returns {error, {crashed, Reason}} if the worker dies first. Intended for
callers that want the explicit-lifecycle features (cached GRV, module-controlled
retry) without managing the worker.
Like start_link/3 but without linking the worker to the caller.
Starts (and links) a transaction worker for Module.
Opts must contain {db, Db}. Optional keys: read_version
(undefined | V, default undefined), owner (default the caller),
ref (correlation token, default a fresh make_ref/0), max_retries
(default infinity).
Like start_link/3 but creates a monitor and does not create a link.