Reference

Everything FlowChassis 0.1.0 offers, in one page. The package documentation is on pkg.go.dev, and the exact rules are in the contract.

The Engine

Call What it does
Open(path, Options) Opens or creates the journal file and locks it against other processes
New(store, Options) The same over any Store. NewMemStore keeps the journal in memory, for tests
Register(Flow) Adds a flow. Runs of it already in the journal start or resume
Start(flow, id, input) Starts a run. An empty id gets a random one. The same id with the same flow and input returns the existing run; with anything else, ErrConflict
Wait(ctx, id) Blocks until the run finishes, and returns it
Get(id) One run, as a RunInfo
List() Every run, in the order they started
Cancel(id) Stops a run and undoes its completed steps
Compact(before) Rewrites the journal with one record per run, leaving out runs that finished before before
Close() Stops the engine. A step interrupted by Close runs again on the next open
Err() Why the engine stopped, if a journal write failed

Options has three fields: Workers, the number of runs that execute at once (default 4); Retry, the policy for steps that don’t set one; and Now, a clock for tests.

Flows and Steps

A Flow is a Name and a list of Steps. A run goes from each step to the one after it, unless a step chooses another with Ctx.Next. It completes after its last step.

Step field What it does
Name Identifies the step in the journal. Unfinished runs resume by it, so keep it stable
Do Performs the step. Returning nil completes it and saves its state changes. An error throws them away and retries
Undo Reverses the step during compensation, seeing the state as the step left it. Nil means nothing to undo
Retry A RetryPolicy for this step’s Do and Undo

A RetryPolicy has MaxAttempts (the first attempt included), Backoff (the first wait), Multiplier and MaxBackoff (a cap). Fields left at zero come from Options.Retry, then from the default: 3 attempts, starting at 100 ms and doubling. A zero MaxBackoff means no cap. Attempts cut off by a crash count; attempts cut off by Close don’t. Wrap an error in flowchassis.Permanent to skip the retries.

Inside a Step

On *Ctx What it gives
the value itself A context.Context, canceled by Cancel or Close
Input() The run’s input
Get, Set, Delete The run’s state: named byte values, kept only when the step completes
Next(name) The step to go to next. Next("") ends the run as completed
Key An idempotency key: run/step/seq, plus /undo in an undo. The same on every attempt and after a restart
Attempt This attempt’s number, counted across restarts
Run, Flow, Step, Seq, Undoing Where the step is

Statuses

Status Meaning
pending Started; no step has begun, or its flow isn’t registered in this process yet
running A step is under way or about to begin
retrying A step failed and waits for its next attempt; RetryAt says when
compensating Undoing completed steps, newest first
completed Every step finished
compensated The run failed or was canceled, and every completed step with an Undo was undone
failed An undo kept failing, so the run stopped part way through compensation and needs a person

Errors

Error When
ErrUnknownFlow Start names a flow that isn’t registered
ErrConflict Start reuses a run ID with another flow or input
ErrNotFound No run has that ID
ErrFinished Cancel on a run that has ended
ErrLocked Another process has the journal open
ErrCorrupt The journal is damaged somewhere other than a torn final write
ErrStopped A journal write failed, so the engine stopped running steps
ErrClosed The engine was closed

What a Crash Does

  • During a step. The step runs again on the next start, attempt number one higher. Steps before it don’t. A step that crashes the process every time gives up at MaxAttempts, and the run compensates.
  • During Close. A step that returns an error because its context was canceled runs again on the next open, as the same attempt. A step that completes anyway is recorded.
  • During an undo. That undo runs again. Undos already done don’t.
  • While a step waits to retry. The wait goes on from the time recorded in the journal.
  • In the middle of a journal write. The torn record is cut off on open and kept in a .torn file beside the journal, and the run carries on from the record before it. A tear counts as one only if nothing whole follows it; damage anywhere else is refused with ErrCorrupt and the file is left as it was.
  • When the disk is full. The engine stops before the next step, writes nothing more, and resumes from the last good record once the journal opens again.

The Journal

One append-only file: an 8-byte header, then records, each a 4-byte length, a 4-byte CRC-32C checksum and a JSON object. Every record names its run and type: start, begin, done, fail, yield, cancel, comp, ubegin, udone, ufail, uyield, end, or snapshot and sstate after compaction. A run’s status, state and history follow from its records in order. The journal format has every field.

The Command-Line Tool

Command What it does
flowchassis runs JOURNAL [-status S] [-json] Lists runs: status, current step, attempt, error
flowchassis show JOURNAL RUN [-json] One run: input, state, and every record with its time
flowchassis check JOURNAL Verifies every record’s checksum and counts runs by status. Exits 0 when good, 1 when damaged, 3 when it ends in a torn write
flowchassis compact JOURNAL [-drop-finished-before D] Rewrites the journal with one record per run
flowchassis demo The crash demo: worker processes killed mid-flow, then every order checked
flowchassis version The version and journal format

runs, show and check read the journal without locking it, so they work while a program has it open. compact takes the lock.

Limits

One process per journal, on one machine. Steps run at least once, not exactly once. A run’s input and each step’s state must fit in a 16 MB record. A finished run that Compact drops is forgotten, so its ID can be started again. There is no server, dashboard or graphical designer.