Hi everyone, I’m relatively new to Elixir. I have the following DynamicSupervisor scenario:
For the supervisor:
defmodule Adt.ClockSupervisor do
use DynamicSupervisor
@me __MODULE__
def start_link(_) do
DynamicSupervisor.start_link(@me, :no_args, name: @me)
end
def init(:no_args) do
DynamicSupervisor.init(strategy: :one_for_one)
end
def create_clock({wt, rs, nt, nw, ns}) do
spec = { Adt.Clock, {wt, rs, nt, nw, ns}}
{:ok, _pid} = DynamicSupervisor.start_child(@me, spec)
end
def all_clocks() do
DynamicSupervisor.which_children(@me)
end
end
And for the supervised (Clock),
defmodule Adt.Clock do
use GenServer, restart: :transient
import Adt.ClockState
alias Adt.ClockState
@me __MODULE__
# API
def start_link({wt, rs, nt, nw, ns}) do
GenServer.start_link(@me, {wt, rs, nt, nw, ns}, name: @me)
end
def start() do
GenServer.cast(@me, :tick)
end
def watch() do
GenServer.call(@me, :watch)
end
def set(new_now) do
GenServer.call(@me, {:watch, new_now})
end
def timer(millis) do
GenServer.call @me, {:timer, millis}
end
def stop() do
GenServer.cast @me, :stop
end
def timer(state, req) do
%ClockState{withtimer: wt, now: t, tmr: tm, res: rs, nav_tick: nt, nav_watch: nw, nav_set: ns} = state
if wt do
if req < 10*nt do
# If the noise average is less than 10 times higher, we do not introduce noise
MicroTimer.usleep(req)
%ClockState{withtimer: wt, now: t, tmr: req, res: rs, nav_tick: nt, nav_watch: nw, nav_set: ns}
else
wait = req + round(abs(:rand.normal(nt, :math.sqrt(nt))))
MicroTimer.usleep(wait)
%ClockState{withtimer: wt, now: t, tmr: wait, res: rs, nav_tick: nt, nav_watch: nw, nav_set: ns}
end
else
%ClockState{withtimer: wt, now: t, tmr: tm, res: rs, nav_tick: nt, nav_watch: nw, nav_set: ns}
end
end
# server
def init({wt, rs, nt, nw, ns}) do
IO.puts "Clock initialized"
{:ok, %ClockState{withtimer: wt, now: 0, tmr: 0, res: rs, nav_tick: nt, nav_watch: nw, nav_set: ns}}
end
defp increment(state) do
%ClockState{withtimer: _, now: _, tmr: _, res: rs, nav_tick: nt, nav_watch: _, nav_set: _} = state
# Generate a random deviate with the square root of the noise average as standard deviation
ndev = round(abs(:rand.normal(nt, :math.sqrt(nt)))) + rs
# Wait the required amount of microseconds
MicroTimer.usleep(ndev)
IO.puts "Tick length: #{ndev}\n"
GenServer.cast @me, {:increment, ndev}
end
defp schedule_tick() do
IO.puts "ticking"
GenServer.cast @me, :tick
end
def handle_cast(:tick, state) do
increment(state)
schedule_tick()
{:noreply, state}
end
def handle_cast(:stop, state) do
{:stop, "Clock operation finalized", state}
end
def handle_cast({:increment, inc}, state) do
%ClockState{withtimer: wt, now: t, tmr: tm, res: rs, nav_tick: nt, nav_watch: nw, nav_set: ns} = state
{:noreply, %ClockState{withtimer: wt, now: t + inc, tmr: tm, res: rs, nav_tick: nt, nav_watch: nw, nav_set: ns}}
end
def handle_call(:watch, _from, state) do
%ClockState{withtimer: wt, now: t, tmr: tm, res: rs, nav_tick: nt, nav_watch: nw, nav_set: ns} = state
# Introduce a small amount of variance from watching the clock
wdev = round(abs(:rand.normal(nt, :math.sqrt(nw))))
new_state = %ClockState{withtimer: wt, now: t + wdev, tmr: tm, res: rs, nav_tick: nt, nav_watch: nw, nav_set: ns}
{:reply, new_state, new_state}
end
def handle_call({:set, new_now}, _from, state) do
%ClockState{withtimer: wt, now: _, tmr: tm, res: rs, nav_tick: nt, nav_watch: nw, nav_set: ns} = state
# Introduce a slightly larger amount of variance from setting the clock
sdev = round(abs(:rand.normal(nt, :math.sqrt(nw))))
new_state = %ClockState{withtimer: wt, now: new_now + sdev, tmr: tm, res: rs, nav_tick: nt, nav_watch: nw, nav_set: ns}
{:reply, new_state, new_state}
end
def handle_call({:timer, millis}, _from, state) do
new_state = timer(state, millis)
{:reply, new_state, new_state}
end
end
If I call
Adt.ClockSupervisor.start_link 0
Adt.ClockSupervisor.create_clock {true, 1000000, 10000, 10000, 50000}
Adt.ClockSupervisor.create_clock {true, 1000000, 10000, 10000, 50000}
the outcome is
** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.261.0>}}
(adt) lib/adt/clksupervisor.ex:16: Adt.ClockSupervisor.create_clock/1
What mistake am I making here that prevents me from spawning more children? I am following code from Dave Thomas’ book (Programming Elixir <= 1.6).
In my use case, I also need to have access to the PID of each clock in order to refer to it in another agent, which consults it. Does this requirement relate to the problem above?
Thanks in advance.