Unable to create more than one child: DynamicSupervisor

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.

You are giving the same name multiple time…

Thats is why You receive

{:error, {:already_started, #PID<0.261.0>}}

You need a unique name, and You might use Registry to find the process back.

2 Likes

Thank you. I just found an appropriate guide based on your answer.