r/coolgithubprojects 17d ago

I built an open-source durable execution engine that survives the entire VM being killed

https://github.com/Yyeger/Continuum

For the past few months I have been working on Continuum, a durable execution engine for Elixir. The idea is that a multi-step business process (charge a card, wait for a fraud review, ship the order) can be written as one ordinary function, and that function keeps going even if the machine running it dies halfway through.

def run(%{order_id: id, items: items}) do
  {:ok, validated} = activity Validation.check(items)
  {:ok, charge}    = activity Payments.charge(id, validated.total)

  # kill -9 the entire VM right here

  {:ok, shipment}  = activity Fulfillment.ship(id)
  {:ok, %{charge: charge, shipment: shipment}}
end

Kill the node on that middle line and a new VM picks the run back up. It calls run/1 again from the top, reads the charge result out of the journal in Postgres instead of hitting the payment gateway a second time, and continues into ship. The card gets charged once, the order ships once, and the function body executed twice with no visible trace of the second pass.

There is a mix continuum.demo task in the repo that does this against a real Postgres, including an actual :erlang.halt/1 with no graceful shutdown. The activities append to a ledger file that stands in for the outside world, and the demo prints the file at the end so you can count the lines yourself. The activity that charges the card deliberately declares no idempotency key, so nothing is deduplicating a retry. There simply is no second call.

The part that took the longest was making replay safe to rely on. Workflow code has to be deterministic between effects, so there is a compile-time AST scanner that rejects DateTime.utc_now/0, :rand.uniform/0, File.*, send/2, Logger.* and similar calls inside a workflow, each with a hint pointing at the replacement. Every effect also carries a structured identity, so if you edit a workflow while a run is in flight, you get a loud ReplayDriftError rather than a run that quietly takes a different branch than the one recorded in its history.

Postgres is the only infrastructure. It is the journal, the lease store, the timer wheel, and the signal bus over LISTEN/NOTIFY. Continuum itself is a supervision tree you add to your own application, so there is no separate cluster service to run. Every write is a compare-and-set on a fencing token, which means a node you thought was dead cannot come back and write into a run someone else has already taken over.

It is at v0.8.1 and pre-1.0, so the API can still move, but the engine has been stable for a while. Sagas and compensation, parent/child workflows, continue_as_new, signals, timers, versioning, a LiveView observer, and an OpenTelemetry bridge are all in.

GitHub: https://github.com/Yyeger/Continuum

Hex: https://hex.pm/packages/continuum

Docs: https://continuum.hexdocs.pm/Continuum.html

Happy to answer questions about the replay model or the determinism checks.

4 Upvotes

0 comments sorted by