2
votes

Heeey all,

I'm kinda new to elixir and quite lost with setting worker names and id's for workers in elixir and I hope that someone can help me out a bit.

My Application file

defmodule Squareup.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  def start(_type, _args) do
    children = [
      # Starts a worker by calling: Squareup.Worker.start_link(arg)
      # {Squareup.Worker, arg}
      Journey,
      Squareup.JourneySupervisor,
      {Squareup.DynamicJourneySupervisor, strategy: :one_for_one, name: Squareup.DynamicJourneySupervisor}
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: Squareup.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

So I startup 2 supervisors a regular one and a dynamic one:

The regular one:

defmodule Squareup.JourneySupervisor do

  use Supervisor

  def start_link(opts) do
    Supervisor.start_link(__MODULE__, opts, name: :my_test_123)
  end

  @impl true
  def init(_init_arg) do
    children = [
      # Starts a worker by calling: Squareup.Worker.start_link(arg)
      # {Squareup.Worker, arg}
      {Queue, [:child_one]},
      {Queue, [:child_two]},
    ]
    Supervisor.init(children, strategy: :one_for_one)
  end

  def child_spec(opts) do
    %{
      id: :my_test_123s,
      start: {__MODULE__, :start_link, [opts]},
      shutdown: 5_000,
      restart: :permanent,
      type: :supervisor
    }
  end
end

The Dynamic One:

defmodule Squareup.DynamicJourneySupervisor do
  use DynamicSupervisor

  def start_link(init_arg) do
    DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  def start_child(init_args) do
    # If MyWorker is not using the new child specs, we need to pass a map:
    # spec = %{id: MyWorker, start: {MyWorker, :start_link, [foo, bar, baz]}}
    spec = {Queue, init_args}
    DynamicSupervisor.start_child(__MODULE__, spec)
  end

  @impl true
  def init(init_arg) do
    DynamicSupervisor.init(strategy: :one_for_one)
  end
end

The normal supervisor a module called Queue

defmodule Queue do
  require Logger
  use GenServer

  ### GenServer API
  def init(state), do: {:ok, state}
  def handle_call(:dequeue, _from, [value | state]) do {:reply, value, state} end
  def handle_call(:dequeue, _from, []), do: {:reply, nil, []}
  def handle_call(:queue, _from, state), do: {:reply, state, state}
  def handle_cast({:enqueue, value}, state) do {:noreply, state ++ [value]} end

  ### Client API / Helper functions

  def start_link(state \\ []) do
    Logger.info("Initializing queue with state:")
    Logger.info(inspect(state))
    GenServer.start_link(__MODULE__, state, name: List.first(state))
  end

  def child_spec(opts) do
    Logger.info("Initializing child_spec:")
    Logger.info(inspect(opts))
    %{
      id: List.first(opts),
      start: {__MODULE__, :start_link, [opts]},
      shutdown: 5_000,
      restart: :permanent,
      type: :worker
    }
  end

  def queue, do: GenServer.call(__MODULE__, :queue)
  def enqueue(value), do: GenServer.cast(__MODULE__, {:enqueue, value})
  def dequeue, do: GenServer.call(__MODULE__, :dequeue)
end

When I start the application I this:

12:55:45.744 [info]  Initializing child_spec:
12:55:45.749 [info]  [:child_one]
12:55:45.749 [info]  Initializing child_spec:
12:55:45.749 [info]  [:child_two]
12:55:45.749 [info]  Initializing queue with state:
12:55:45.749 [info]  [:child_one]
12:55:45.749 [info]  Initializing queue with state:
12:55:45.749 [info]  [:child_two]

iex(4)> Supervisor.which_children(:my_test_123)
[
  {:child_two, #PID<0.218.0>, :worker, [Queue]},
  {:child_one, #PID<0.217.0>, :worker, [Queue]}
]

But when I tell the Dynamic Supervisor to startup the process

Squareup.DynamicJourneySupervisor.start_child([:my_dyname])
12:56:07.329 [info]  Initializing child_spec:
12:56:07.329 [info]  [:my_dyname]
12:56:07.330 [info]  Initializing queue with state:
12:56:07.330 [info]  [:my_dyname]

and look up the worker names, then they are always :undefined

iex(5)> Supervisor.which_children(Squareup.DynamicJourneySupervisor)
[{:undefined, #PID<0.224.0>, :worker, [Queue]}]

The id of the worker seems to be set correctly. Is there a way to set the name when a process is started via the DynamicSupervisor?

Already many thanks in advance!

Leon

1

1 Answers

1
votes

It seems you just misinterpreted the returned values from which_children/1 function. It returns a list with information about all children actually.

From the docs:

* id - it is always :undefined for dynamic supervisors

Hence you are actually naming your processes correctly. Just your client code for Queue module is incorrect. You should fix your functions queue enqueue and dequeue to actually call a named GenServer if you want to access those.

What I mean is they should look like this for example:

  def queue(name \\ __MODULE__), do: GenServer.call(name, :queue)
  def enqueue(name \\ __MODULE__, value), do: GenServer.cast(name, {:enqueue, value})
  def dequeue(name \\ __MODULE__), do: GenServer.call(name, :dequeue)

This way GenServer.call/2 will always call the named GenServers. You can also access them not by the names but by their pids (which you can actually receive from which_children/1 function dynamically if you need).