Packages
finitomata
0.24.1
0.41.0
0.40.0
0.35.0
0.34.0
0.33.0
0.32.0
0.31.1
0.30.3
0.30.2
0.30.1
0.30.0
0.29.10
0.29.9
0.29.8
0.29.7
0.29.6
0.29.5
0.29.4
0.29.3
0.29.2
0.29.1
0.29.0
0.28.1
0.28.0
0.27.1
0.27.0
0.26.4
0.26.3
0.26.2
0.26.1
0.26.0
0.25.0
0.24.4
0.24.3
0.24.2
0.24.1
0.24.0
0.23.7
0.23.6
0.23.5
0.23.4
0.23.3
0.23.2
0.23.1
0.23.0
0.22.1
0.22.0
0.21.4
0.21.3
0.21.2
0.21.1
0.21.0
0.20.2
0.20.1
0.20.0
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.1
0.17.0
0.16.0
0.15.1
0.15.0
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.0
0.12.1
0.12.0
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.1
0.1.0
The FSM implementation generated from PlantUML textual representation.
Current section
Files
Jump to
Current section
Files
lib/infinitomata.ex
defmodule Infinitomata do
@moduledoc """
The sibling of `Finitomata`, but runs transparently in the cluster.
If you want to use a _stateful consistent hash ring_ like [`libring`](https://hexdocs.pm/libring),
implement the behaviour `Finitomata.ClusterInfo` wrapping calls to it and
invoke `Finitomata.ClusterInfo.init(Impl)` before using `Infinitomata.start_fsm/4`.
The example of such an implementation for `libring` (assuming the named ring `@ring`
has been started in the supervision tree) follows.
```elixir
defmodule MyApp.ClusterInfo do
@moduledoc false
@behaviour Finitomata.ClusterInfo
@impl Finitomata.ClusterInfo
def nodes, do: HashRing.nodes(@ring)
@impl Finitomata.ClusterInfo
def whois(id), do: HashRing.key_to_node(@ring, id)
end
```
"""
@moduledoc since: "0.15.0"
require Logger
alias Finitomata.{ClusterInfo, State, Transition}
alias Finitomata.Distributed.GroupMonitor, as: InfMon
alias Finitomata.Distributed.Supervisor, as: InfSup
alias Finitomata.Supervisor, as: FinSup
@doc since: "0.16.0"
def start_link(id \\ nil, nodes \\ Node.list()) do
InfSup.start_link(id, nodes)
end
@doc since: "0.16.0"
def child_spec(id \\ nil) do
Supervisor.child_spec({InfSup, id}, id: {InfSup, id})
end
defp distributed_call(fun, id, target, args) do
case InfSup.get(id, target) do
%{node: node} ->
with {:badrpc, error} <-
:rpc.call(node, Finitomata, fun, [id, target | List.wrap(args)]) do
Logger.error(
"[♻️] Distributed: " <> inspect(id: id, node: node, target: target, error: error)
)
:ok = synch(id)
distributed_call(fun, id, target, args)
end
nil ->
Process.sleep(1)
distributed_call(fun, id, target, args)
_ ->
{:error, :not_started}
end
end
@doc since: "0.16.0"
@doc "Count of children"
def count(id \\ nil) do
id
|> FinSup.infinitomata_name()
|> InfMon.count()
end
@doc since: "0.16.0"
@doc "The full state with all the children acrosss the cluster, might be a heavy map"
@spec all(Finitomata.id()) :: %{
optional(Finitomata.fsm_name()) => %{pid: pid(), node: node(), reference: reference()}
}
def all(id \\ nil) do
id
|> FinSup.infinitomata_name()
|> InfSup.all()
end
@doc since: "0.18.0"
@doc "Returns the random _FSM_ from the pool"
def random(id \\ nil) do
id
|> all()
|> Map.keys()
|> Enum.random()
end
@doc since: "0.19.0"
@doc "Synchronizes the local `Infinitomata` instance with the cluster"
def synch(id \\ nil) do
InfSup.synch(id, FinSup.infinitomata_name(id))
end
@doc """
Starts the FSM somewhere in the cluster.
See `Finitomata.start_fsm/4`.
"""
@doc since: "0.15.0"
@spec start_fsm(Finitomata.id(), Finitomata.fsm_name(), module(), any()) ::
DynamicSupervisor.on_start_child()
def start_fsm(id \\ nil, target, implementation, payload) do
id = FinSup.infinitomata_name(id)
case InfSup.get(id, target) do
nil ->
node = ClusterInfo.whois({id, target})
do_start_fsm(node == node(), node, id, target, implementation, payload)
%{node: node, pid: pid} ->
{:error, {:already_started, {node, pid}}}
end
end
@spec do_start_fsm(boolean(), node(), Finitomata.id(), Finitomata.fsm_name(), module(), any()) ::
DynamicSupervisor.on_start_child()
defp do_start_fsm(false, node, id, target, implementation, payload) do
case :rpc.block_call(node, Finitomata, :start_fsm, [id, target, implementation, payload]) do
{:ok, pid} ->
# local_pid = :rpc.call(node, :erlang, :list_to_pid, [:erlang.pid_to_list(pid)]).
:ok = :rpc.block_call(node, :pg, :join, [InfSup.group(id), pid])
{:ok, pid}
{:error, {:already_started, pid}} ->
{:error, {:already_started, {node, pid}}}
{error, reason} when error in [:error, :badrpc] ->
{:error, reason}
end
end
defp do_start_fsm(true, node, id, target, implementation, payload) do
case Finitomata.start_fsm(id, target, implementation, payload) do
{:ok, pid} ->
:ok = :pg.join(InfSup.group(id), pid)
{:ok, pid}
{:error, {:already_started, pid}} ->
{:error, {:already_started, {node, pid}}}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Initiates the transition in the cluster.
See `Finitomata.transition/4`.
"""
@doc since: "0.15.0"
@spec transition(
Finitomata.id(),
Finitomata.fsm_name(),
Transition.event() | {Transition.event(), State.payload()},
non_neg_integer()
) ::
:ok
def transition(id \\ nil, target, event_payload, delay \\ 0) do
id = FinSup.infinitomata_name(id)
distributed_call(:transition, id, target, [event_payload, delay])
end
@doc """
The state of the FSM in the cluster.
See `Finitomata.state/3`.
"""
@doc since: "0.15.0"
def state(id \\ nil, target, reload? \\ :full) do
id = FinSup.infinitomata_name(id)
distributed_call(:state, id, target, reload?)
end
end