Packages
Postgres-backed durable execution for Elixir: declare an FSM, the engine commits its state before each step proceeds, so instances survive process and node death and resume where they left off.
Current section
Files
Jump to
Current section
Files
guides/jobs.md
# Jobs
The simplest durable unit is a **job**: one function that runs to completion, with retries
for free. Define `perform/1` or `perform/2` (instead of `step/2`) and you get a one-shot
durable job — no step names, no outcome tuples.
```elixir
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](machines.md#state) is declared) and, in the `/2` form, the
`t:GenDurable.Context.t/0`.
## Return values
| Return | Result |
|---|---|
| `:ok` | terminal — `done`, empty result |
| `{:ok, map}` | terminal — `done`, `map` recorded as the result |
| `{:error, reason}` | retried with `backoff/1` until `:max_attempts`, then `failed` |
| `{:cancel, reason}` | `failed` immediately, no retry |
| a raised exception | treated as `{:error, exception}` |
## Retries and backoff
`{:error, _}` (or a raised exception) is retried until `:max_attempts` (default `20`), then
the instance is `failed`. The delay between attempts is `backoff/1` — a capped exponential by
default (`min(1000 * 2^attempt, 300_000)` ms). Override either:
```elixir
defmodule Charge do
use GenDurable.FSM, max_attempts: 10
@impl true
def perform(args, ctx) do
case Stripe.charge(args["amount"]) do
:ok -> :ok
{:error, :card_declined} -> {:cancel, :declined} # don't retry a hard decline
{:error, _transient} -> {:error, :try_again} # retried with backoff
end
end
@impl true
def backoff(attempt), do: 5_000 * (attempt + 1) # linear, 5s steps
end
```
`ctx.attempt` (0-based) is available in `perform/2` and `backoff/1`.
## When you outgrow a job
A job is a degenerate one-step machine. The moment you need to wait for an external event,
fan work out, or move through named phases, switch to [`step/2`](machines.md) — a module
defines **either** `perform` **or** `step/2`, never both.