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?