DEV Community

NDREAN
NDREAN

Posted on • Updated on

A note on Dynamic Supervised GenServers

The reference is this lesson. We can start a GenServer "on-the-fly" when using an atom as identifier. For example:

Order.start_link(name: :a)

Every client function is then called using this given name: Order.deliver(:a)

defmodule Order do
  use GenServer

  def start_link(name: name) do
    {:ok, _pid} = GenServer.start_link(__MODULE__, name, name: name)
  end

  def show(name), do: GenServer.call(name, :show)

  def add(name, value), do: GenServer.call(name, {:add, value})

  def deliver(name), do: GenServer.cast(name, :deliver)



  def init(name = _state), do: {:ok, {name, :init, 0}}

  def handle_call(:show, _from, {_n, step, value} = state) do
    {:reply, {step, value}, state}
  end

  [... server callbacks...]
end
Enter fullscreen mode Exit fullscreen mode

You need to use an atom to name it (or its PID or a global name or use :via). The docs on name registration explain this.

We want to supervise this process once the "name" is determined.
We have to use the Registry and a dynamic supervisor. This supervisor and this Registry are in the supervision tree, instantiated here in the Application module.

use Application

def start(_, _) do
    [
      {Registry, keys: :unique, name: MyRegistry},
      {DynamicSupervisor, name: MyDynSup}
    ]
    |> Supervisor.start_link(opts)
end
Enter fullscreen mode Exit fullscreen mode

You use the :via strategy and supervise the previous "Order" module. Each new process is started "manually" with start_child.
Create a new module with the modified client functions where the reference to the argument "name" in the client functions is replaced by "via_tuple(name)" as below. The new module wraps the original "Order" GenServer: just reformulate the client functions as below. The server functions are those defined in the "Order" module.

defmodule DynOrder do

  defp via_tuple(name) do
    {:via, Registry, {MyRegistry, name}}
  end

  def new(name) do
    DynamicSupervisor.start_child(
      DynSup,
      {Order, [name: via_tuple(name)]}
    )
  end


  def show(name), do: via_tuple(name) |> Order.show()

  def add(name, value), do: via_tuple(name) |> Order.add(value)

  def deliver(name), do: via_tuple(name) |> Order.deliver()
end
Enter fullscreen mode Exit fullscreen mode

We can now start the new dynamic supervised GenServer with any string, say: DynOrder.new("a") or DynOrder.new("b").
Then we can do: DynOrder.deliver("a") or DynOrder.show("b").

We can programmatically assign names and run many concurrent supervised processes:

Enum.each(1..100, fn i -> 
   DynOrder.new("a-#{i}");
   DynOrder.deliver("a_#{i}")
end)

Enter fullscreen mode Exit fullscreen mode

You can even go one step further with partition supervisor.

Top comments (0)