🎓 A1Learner
🔍
0 of 46 lessons completed

Elixir Dynamic Supervisors

A dynamic supervisor is a supervisor that starts children on demand instead of from a fixed list at boot. Children that are not known when the program starts can join later.

A child starts on demand

The dynamic supervisor starts empty. One start_child call adds a worker that answers 10.

defmodule Worker do
  use GenServer
  def start_link({name, initial}), do: GenServer.start_link(__MODULE__, initial, name: name)
  def init(initial), do: {:ok, initial}
  def handle_call(:get, _from, state), do: {:reply, state, state}
  def handle_cast(:inc, state), do: {:noreply, state + 1}
end
{:ok, dyn} = DynamicSupervisor.start_link(strategy: :one_for_one)
DynamicSupervisor.start_child(dyn, %{id: :a, start: {Worker, :start_link, [{:a, 10}]}})
IO.inspect(GenServer.call(:a, :get))
DynamicSupervisor.stop(dyn)

Children join one at a time

Two start_child calls add two workers. The count of children is 2, and both answer.

defmodule Worker do
  use GenServer
  def start_link({name, initial}), do: GenServer.start_link(__MODULE__, initial, name: name)
  def init(initial), do: {:ok, initial}
  def handle_call(:get, _from, state), do: {:reply, state, state}
  def handle_cast(:inc, state), do: {:noreply, state + 1}
end
{:ok, dyn} = DynamicSupervisor.start_link(strategy: :one_for_one)
DynamicSupervisor.start_child(dyn, %{id: :a, start: {Worker, :start_link, [{:a, 10}]}})
DynamicSupervisor.start_child(dyn, %{id: :b, start: {Worker, :start_link, [{:b, 20}]}})
IO.inspect(DynamicSupervisor.which_children(dyn) |> length())
IO.inspect(GenServer.call(:a, :get))
IO.inspect(GenServer.call(:b, :get))
DynamicSupervisor.stop(dyn)

One dynamic child can be stopped alone

Two workers run. Stopping the first leaves one child, and the second still answers 20.

defmodule Worker do
  use GenServer
  def start_link({name, initial}), do: GenServer.start_link(__MODULE__, initial, name: name)
  def init(initial), do: {:ok, initial}
  def handle_call(:get, _from, state), do: {:reply, state, state}
  def handle_cast(:inc, state), do: {:noreply, state + 1}
end
{:ok, dyn} = DynamicSupervisor.start_link(strategy: :one_for_one)
DynamicSupervisor.start_child(dyn, %{id: :a, start: {Worker, :start_link, [{:a, 10}]}})
DynamicSupervisor.start_child(dyn, %{id: :b, start: {Worker, :start_link, [{:b, 20}]}})
pid = GenServer.whereis(:a)
DynamicSupervisor.terminate_child(dyn, pid)
IO.inspect(DynamicSupervisor.which_children(dyn) |> length())
IO.inspect(GenServer.call(:b, :get))
IO.inspect(GenServer.whereis(:a) == nil)
DynamicSupervisor.stop(dyn)

Example

defmodule Worker do
  use GenServer
  def start_link({name, initial}), do: GenServer.start_link(__MODULE__, initial, name: name)
  def init(initial), do: {:ok, initial}
  def handle_call(:get, _from, state), do: {:reply, state, state}
  def handle_cast(:inc, state), do: {:noreply, state + 1}
end
{:ok, dyn} = DynamicSupervisor.start_link(strategy: :one_for_one)
DynamicSupervisor.start_child(dyn, %{id: :a, start: {Worker, :start_link, [{:a, 10}]}})
GenServer.cast(:a, :inc)
IO.inspect(GenServer.call(:a, :get))
DynamicSupervisor.start_child(dyn, %{id: :b, start: {Worker, :start_link, [{:b, 20}]}})
Process.exit(GenServer.whereis(:a), :kill)
Process.sleep(100)
DynamicSupervisor.which_children(dyn)
IO.inspect(GenServer.call(:a, :get))
IO.inspect(GenServer.call(:b, :get))
IO.inspect(DynamicSupervisor.which_children(dyn) |> length())
DynamicSupervisor.stop(dyn)

Key points

  • Dynamic supervisor: a supervisor with no fixed children list.
  • start_child: the function that adds one child to a running dynamic supervisor.
  • On demand: a child starts when the program asks for it, not when the supervisor starts.
  • terminate_child: the function that stops one dynamic child while the others keep running.
  • Restart: a failed dynamic child is restarted by the same restart rule as a fixed child.

In this example, a dynamic supervisor starts empty and a first child joins on demand and is raised to 11. A second child then joins, and the first child fails. After the failure, the first child answers 10 again, the second still answers 20, and the child count is still 2. The dynamic supervisor is stopped at the end.

Note

dynamic children still need a child specification, even though they start later.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

When is a dynamic supervisor child started?