GenDurable.FSM behaviour (gen_durable v0.1.2)

Copy Markdown View Source

Behaviour for a durable FSM — or, in its degenerate one-step form, a durable job.

A module defines either step/2 (a state machine) or perform/1/perform/2 (a one-shot job), never both.

Job form (one step)

Define perform/1 or perform/2 and you get a durable job — no step names, no outcome tuples:

defmodule Cleanup do
  use GenDurable.FSM

  @impl true
  def perform(args, _ctx) do
    File.rm_rf!(args["path"])
    :ok
  end
end

GenDurable.insert(Cleanup, args: %{"path" => "/tmp/x"})

perform receives the instance args (the plain map passed as :args/:state, or the typed struct if a State schema is declared) and, in the /2 form, the GenDurable.Context.t/0. It returns:

  • :ok / {:ok, result_map} — the job is done.
  • {:error, reason} — retried with backoff/1 until :max_attempts, then failed.
  • {:cancel, reason}failed immediately, no retry.
  • a raised exception — treated as {:error, exception} (routed through handle/2).

:max_attempts (default 20) and backoff/1 (default a capped exponential) tune retries. Override backoff/1 to change the schedule.

FSM form (many steps)

defmodule Checkout do
  use GenDurable.FSM, version: 1, queue: "checkout"

  defmodule State do
    use GenDurable.State

    embedded_schema do
      field :n, :integer, default: 0
    end
  end

  @impl true
  def step("start", %{state: s}), do: {:next, "await_pay", %{s | n: s.n + 1}}
  def step("await_pay", ctx) do
    case Enum.find(ctx.signals, & &1.name == "payment_confirmed") do
      nil -> {:await, "payment_confirmed", ctx.state}
      sig -> {:next, "ship", apply_payment(ctx.state, sig)}
    end
  end
  def step("ship", _ctx), do: {:done, %{"shipped" => true}}

  @impl true
  def handle(reason, ctx) do
    if ctx.attempt < 5, do: {:replay, ctx.state, backoff(ctx.attempt)}, else: {:stop, reason}
  end
end

use options

  • :name — FSM name stored in the fsm column (default: inspect(module)).
  • :versionfsm_version (default 1). Old versions coexist as separate registered modules; see GenDurable.Registry.
  • :queue — default queue for instances (default "default").
  • :state — the GenDurable.State embedded-schema module. Optional: if a nested State schema module is defined inside the FSM (as above) it is picked up by convention, so you rarely pass this. Omit both for plain-map state.
  • :initial — initial step for GenDurable.insert/2 (default "start").
  • :max_attempts — job retry cap (default 20). Job form only.

For the FSM form, handle/2 defaults to {:stop, reason} and is overridable. For the job form, both handle/2 (retry-with-backoff) and backoff/1 are generated and overridable; overriding handle/2 drops to the FSM outcome contract.

Summary

Types

result()

@type result() :: :ok | {:ok, map()} | {:error, term()} | {:cancel, term()}

Callbacks

backoff(attempt)

(optional)
@callback backoff(attempt :: non_neg_integer()) :: non_neg_integer()

handle(reason, ctx)

(optional)
@callback handle(reason :: term(), ctx :: GenDurable.Context.t()) ::
  GenDurable.Outcome.t() | term()

perform(args)

(optional)
@callback perform(args :: term()) :: result()

perform(args, ctx)

(optional)
@callback perform(args :: term(), ctx :: GenDurable.Context.t()) :: result()

step(step, ctx)

(optional)
@callback step(step :: String.t(), ctx :: GenDurable.Context.t()) ::
  GenDurable.Outcome.t() | term()