Execution engine for FSL.Machine finite state machines.
A scenario module (one that does use FSL.Machine) compiles each state
block into a function __state_<name>/1 that takes the context (FSL.Context,
or whatever struct the application extended it into) and
returns a transition descriptor:
{:goto, target, desc, ctx}— move to another state{:terminal, :success, r, ctx}— scenario completed successfully{:terminal, :failure, r, ctx}— scenario failed
target is either an explicit state name, the atom :next (the next state
declared in the module), :loop (re-enter the current state) or :__back__
(the state entered before this one, read from ctx.laststate). The runner
resolves those, logs the transition and calls the next state function — this
is the "handled by the runner, not a direct recursive call" contract from the
README, which keeps the call stack flat across an arbitrary number of
transitions.
Entry points
FSL.Host.bootstrap/0— start whatever the binding needs (idempotent). Called once, throughrun/2withstart_stack = true.run_instance/1— run a single scenario instance in the calling process (an application typically binds its own events toself(), so the whole FSM must run whererun_instance/1is called).run/2— convenience used by the generatedrun/1: optionally bootstrap the stack, then run one instance.
Summary
Functions
Run one instance of module, optionally bootstrapping its host first.
Build the initial context from the scenario config block and run the FSM
from initial_state until a terminal state is reached. Returns :ok on
success or {:error, reason} on failure.
Start one machine to serve one inbound session, and monitor it.
Functions
Run one instance of module, optionally bootstrapping its host first.
true is the one-shot mode a CLI or a mix task uses: bootstrap whatever the
application needs (FSL.Host.bootstrap/0), then run. false assumes that
has already happened, which is what lets many instances run in parallel over
one set of resources.
Returns :ok, {:error, reason} or {:aborted, reason}.
Build the initial context from the scenario config block and run the FSM
from initial_state until a terminal state is reached. Returns :ok on
success or {:error, reason} on failure.
Start one machine to serve one inbound session, and monitor it.
This is the server-side entry point: something outside FSL accepts a new session — a connection, a call, a request — and needs a machine bound to it, plus a way to know when that machine is done.
{pid, ref} = FSL.Runner.spawn_uas_instance(MyApp.Greeter, parent_pid: self())It returns {pid, ref}, where pid is the process running the machine and
ref is a monitor reference: the caller receives {:DOWN, ref, :process, pid, reason} when the run ends, which is where a pool frees the slot it charged for
this session.
opts are forwarded to run_instance/2. :parent_pid names who is told about
the outcome; :dialog_pid and :inbound_request are the two slots a binding
uses to hand the machine the session it is being started for, and what goes in
them is the binding's business.
target is a module or a path, resolved relative to the current directory
— unlike spawn_fsm, which resolves relative to the file that declares it.
A server target comes from an operator (a command line, a configuration file),
so it is read the way any path a person types is read.
Example: a SIP registrar
In Elixip, a registration module calls
this from on_new_registration/3 with the dialog and the REGISTER it just
received, and returns the pid as {:accept, pid}; the ref frees one slot of
the concurrency quota when the scenario ends.