View Source
Siblings
The partitioned dynamic supervision of FSM-backed workers.
usage
Usage
Siblings
is a library to painlessly manage many uniform processes,
all having the lifecycle and the FSM behind.
Consider the service, that polls the market rates from several diffferent sources, allowing semi-automated trading based on predefined conditions. For each bid, the process is to be spawn, polling the external resources. Once the bid condition is met, the bid gets traded.
With Siblings
, one should implement Siblings.Worker.perform/3
callback, doing actual work and returning either :ok
if no action
should be taken, or {:transition, event, payload}
to initiate the
FSM transition. When the FSM get exhausted (reaches its end state,)
both the performing process and the FSM itself do shut down.
FSM instances leverage Finitomata
library, which should be used alone if no recurrent perform
should be
accomplished or if the instances are not uniform.
Typical code for the Siblings.Worker
implementation would be as follows
defmodule MyApp.Worker do
@fsm """
born --> |reject| rejected
born --> |bid| traded
"""
use Finitomata, @fsm
def on_transition(:born, :reject, _nil, payload) do
perform_rejection(payload)
{:ok, :rejected, payload}
end
def on_transition(:born, :bid, _nil, payload) do
perform_bidding(payload)
{:ok, :traded, payload}
end
@behaviour Siblings.Worker
@impl Siblings.Worker
def perform(state, id, payload)
def perform(:born, id, payload) do
cond do
time_to_bid?() -> {:transition, :bid, nil}
stale?() -> {:transition, :reject, nil}
true -> :noop
end
end
def perform(:rejected, id, _payload) do
Logger.info("The bid #{id} was rejected")
{:transition, :__end__, nil}
end
def perform(:traded, id, _payload) do
Logger.info("The bid #{id} was traded")
{:transition, :__end__, nil}
end
end
Now it can be used as shown below
{:ok, pid} = Siblings.start_link()
Siblings.start_child(MyApp.Worker, "Bid1", %{}, interval: 1_000)
Siblings.start_child(MyApp.Worker, "Bid2", %{}, interval: 1_000)
...
The above would spawn two processes, checking the conditions once
per a second (interval
,) and manipulating the underlying FSM to
walk through the bids’ lifecycles.
Worker’s interval might be reset with
GenServer.cast(pid, {:reset, interval})
and the message might be casted
to it with GenServer.call(pid, {:message, message})
. For the latter
to work, the optional callback on_call/2
must be implemented.
Sidenote: Normally, Siblings
supervisor would be put into
the supervision tree of the target application.
installation
Installation
def deps do
[
{:siblings, "~> 0.1"}
]
end
changelog
Changelog
0.8.0
— updated with lastfinitomata
compiler0.7.0
—Siblings.state/{0,1,2,3}
+ update toFinitoma 0.7
0.5.1
— allow{:reschedule, non_neg_integer()}
return fromperform/3
0.5.0
— use FSM for theSibling.Lookup
0.4.3
— accepthibernate?:
boolean parameter in call toSiblings.start_child/4
to hibernate children0.4.2
— acceptworkers:
in call toSiblings.child_spec/1
to statically initializeSiblings
0.4.1
— [BUG] many namedSiblings
instances0.4.0
—Siblings.{multi_call/2, multi_transition/3}
0.3.3
—Siblings.{state/1, payload/2}
0.3.2
—Siblings.{call/3, reset/3, transition/4}
0.3.1
— retrieve childrens as bothmap
andlist
0.3.0
—GenServer.cast(pid, {:reset, interval})
andGenServer.call(pid, {:message, message})
0.2.0
— FastWorker
lookup0.1.0
— Initial MVP