dgen_transaction behaviour (DGen v0.4.0)

Copy Markdown View Source

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:

  1. 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/2 resets the read version).
  2. Non-blocking commit. A calling process can hand a write to a dgen_transaction worker 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}Result is whatever handle_committed/2 returned.
  • {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 live Tx; replayed on every retry unless handle_retry/2 is 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 to handle_begin/2).
  • handle_conflict/2 — veto or allow a retry on a retryable error (defaults to retry). 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 through handle_conflict/2on_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 terminal Reply.

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

cb_state()

-type cb_state() :: term().

directive()

-type directive() :: {noreply, cb_state()} | {commit, cb_state()} | {stop, term(), cb_state()}.

opt()

-type opt() ::
          {db, dgen_backend:db()} |
          {read_version, undefined | integer()} |
          {owner, pid()} |
          {ref, term()} |
          {max_retries, non_neg_integer() | infinity}.

opts()

-type opts() :: [opt()].

Callbacks

handle_begin(Tx, State)

-callback handle_begin(Tx :: dgen_backend:tx(), State :: cb_state()) -> directive().

handle_call(Msg, From, Tx, State)

(optional)
-callback handle_call(Msg :: term(),
                      From :: gen_server:from(),
                      Tx :: dgen_backend:tx(),
                      State :: cb_state()) ->
                         {reply, term(), cb_state()} | directive().

handle_cast(Msg, Tx, State)

(optional)
-callback handle_cast(Msg :: term(), Tx :: dgen_backend:tx(), State :: cb_state()) -> directive().

handle_committed(CommittedVersion, State)

-callback handle_committed(CommittedVersion :: integer(), State :: cb_state()) -> {ok, Result :: term()}.

handle_conflict(ErrorCode, State)

(optional)
-callback handle_conflict(ErrorCode :: integer(), State :: cb_state()) -> retry | {stop, Reason :: term()}.

handle_retry(Tx, State)

(optional)
-callback handle_retry(Tx :: dgen_backend:tx(), State :: cb_state()) -> directive().

init(Args)

-callback init(Args :: term()) -> {ok, cb_state()} | {stop, Reason :: term()}.

terminate(Reply, State)

(optional)
-callback terminate(Reply :: term(), State :: cb_state()) -> term().

Functions

code_change(OldVsn, St, Extra)

handle_call/3

handle_cast/2

handle_continue/2

handle_info(Info, St)

init(Args)

run(Module, Args, Opts0)

-spec run(module(), term(), opts()) -> term().

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.

start(Module, Args, Opts)

-spec start(module(), term(), opts()) -> {ok, pid()} | {error, term()}.

Like start_link/3 but without linking the worker to the caller.

start_link(Module, Args, Opts)

-spec start_link(module(), term(), opts()) -> {ok, pid()} | {error, term()}.

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).

start_monitor(Module, Args, Opts)

-spec start_monitor(module(), term(), opts()) -> {ok, {pid(), reference()}} | {error, term()}.

Like start_link/3 but creates a monitor and does not create a link.

terminate(Reply, State)