Elixir Supervision Strategies
A supervision strategy is the rule a supervisor follows when one of its children crashes. The rule decides which other children restart along with the crashed one.
One for one restarts only the crashed child
The first worker crashes, and only its changed flag is true. The second worker keeps its process and its value.
defmodule WorkerA do
use GenServer
def start_link(v), do: GenServer.start_link(__MODULE__, v)
def init(v), do: {:ok, v}
def handle_call(:get, _from, v), do: {:reply, v, v}
end
defmodule WorkerB do
use GenServer
def start_link(v), do: GenServer.start_link(__MODULE__, v)
def init(v), do: {:ok, v}
def handle_call(:get, _from, v), do: {:reply, v, v}
end
defmodule Waiter do
def wait(_fun, 0), do: :timeout
def wait(fun, n) do
if fun.() do
:ready
else
Process.sleep(5)
wait(fun, n - 1)
end
end
end
defmodule SupX do
use Supervisor
def start_link(_), do: Supervisor.start_link(__MODULE__, :ok)
def init(:ok) do
Supervisor.init([{WorkerA, "a"}, {WorkerB, "b"}], strategy: :one_for_one)
end
end
{:ok, sup} = SupX.start_link(:ok)
pids = fn ->
Supervisor.which_children(sup)
|> Enum.map(fn {id, pid, _t, _m} -> {id, pid} end)
|> Enum.into(%{})
end
before = pids.()
Process.exit(before[WorkerA], :kill)
Waiter.wait(fn ->
kids = pids.()
is_pid(kids[WorkerA]) and kids[WorkerA] != before[WorkerA]
end, 200)
after_map = pids.()
IO.inspect([after_map[WorkerA] != before[WorkerA], after_map[WorkerB] != before[WorkerB]])
IO.inspect(GenServer.call(after_map[WorkerA], :get))
IO.inspect(GenServer.call(after_map[WorkerB], :get))
Supervisor.stop(sup)One for all restarts every child
The first worker crashes, and both changed flags are true. Every child starts again with its starting value.
defmodule WorkerA do
use GenServer
def start_link(v), do: GenServer.start_link(__MODULE__, v)
def init(v), do: {:ok, v}
def handle_call(:get, _from, v), do: {:reply, v, v}
end
defmodule WorkerB do
use GenServer
def start_link(v), do: GenServer.start_link(__MODULE__, v)
def init(v), do: {:ok, v}
def handle_call(:get, _from, v), do: {:reply, v, v}
end
defmodule Waiter do
def wait(_fun, 0), do: :timeout
def wait(fun, n) do
if fun.() do
:ready
else
Process.sleep(5)
wait(fun, n - 1)
end
end
end
defmodule SupX do
use Supervisor
def start_link(_), do: Supervisor.start_link(__MODULE__, :ok)
def init(:ok) do
Supervisor.init([{WorkerA, "a"}, {WorkerB, "b"}], strategy: :one_for_all)
end
end
{:ok, sup} = SupX.start_link(:ok)
pids = fn ->
Supervisor.which_children(sup)
|> Enum.map(fn {id, pid, _t, _m} -> {id, pid} end)
|> Enum.into(%{})
end
before = pids.()
Process.exit(before[WorkerA], :kill)
Waiter.wait(fn ->
kids = pids.()
is_pid(kids[WorkerA]) and is_pid(kids[WorkerB]) and kids[WorkerA] != before[WorkerA] and kids[WorkerB] != before[WorkerB]
end, 200)
after_map = pids.()
IO.inspect([after_map[WorkerA] != before[WorkerA], after_map[WorkerB] != before[WorkerB]])
IO.inspect(GenServer.call(after_map[WorkerA], :get))
IO.inspect(GenServer.call(after_map[WorkerB], :get))
Supervisor.stop(sup)Rest for one restarts only the crashed child and later children
The last worker crashes, and only its changed flag is true. The earlier worker keeps its process and its value.
defmodule WorkerA do
use GenServer
def start_link(v), do: GenServer.start_link(__MODULE__, v)
def init(v), do: {:ok, v}
def handle_call(:get, _from, v), do: {:reply, v, v}
end
defmodule WorkerB do
use GenServer
def start_link(v), do: GenServer.start_link(__MODULE__, v)
def init(v), do: {:ok, v}
def handle_call(:get, _from, v), do: {:reply, v, v}
end
defmodule Waiter do
def wait(_fun, 0), do: :timeout
def wait(fun, n) do
if fun.() do
:ready
else
Process.sleep(5)
wait(fun, n - 1)
end
end
end
defmodule SupX do
use Supervisor
def start_link(_), do: Supervisor.start_link(__MODULE__, :ok)
def init(:ok) do
Supervisor.init([{WorkerA, "a"}, {WorkerB, "b"}], strategy: :rest_for_one)
end
end
{:ok, sup} = SupX.start_link(:ok)
pids = fn ->
Supervisor.which_children(sup)
|> Enum.map(fn {id, pid, _t, _m} -> {id, pid} end)
|> Enum.into(%{})
end
before = pids.()
Process.exit(before[WorkerB], :kill)
Waiter.wait(fn ->
kids = pids.()
is_pid(kids[WorkerB]) and kids[WorkerB] != before[WorkerB]
end, 200)
after_map = pids.()
IO.inspect([after_map[WorkerA] != before[WorkerA], after_map[WorkerB] != before[WorkerB]])
IO.inspect(GenServer.call(after_map[WorkerA], :get))
IO.inspect(GenServer.call(after_map[WorkerB], :get))
Supervisor.stop(sup)Example
defmodule WorkerA do
use GenServer
def start_link(v), do: GenServer.start_link(__MODULE__, v)
def init(v), do: {:ok, v}
def handle_call(:get, _from, v), do: {:reply, v, v}
end
defmodule WorkerB do
use GenServer
def start_link(v), do: GenServer.start_link(__MODULE__, v)
def init(v), do: {:ok, v}
def handle_call(:get, _from, v), do: {:reply, v, v}
end
defmodule Waiter do
def wait(_fun, 0), do: :timeout
def wait(fun, n) do
if fun.() do
:ready
else
Process.sleep(5)
wait(fun, n - 1)
end
end
end
defmodule SupX do
use Supervisor
def start_link(_), do: Supervisor.start_link(__MODULE__, :ok)
def init(:ok) do
Supervisor.init([{WorkerA, "a"}, {WorkerB, "b"}], strategy: :rest_for_one)
end
end
{:ok, sup} = SupX.start_link(:ok)
pids = fn ->
Supervisor.which_children(sup)
|> Enum.map(fn {id, pid, _t, _m} -> {id, pid} end)
|> Enum.into(%{})
end
before = pids.()
Process.exit(before[WorkerA], :kill)
Waiter.wait(fn ->
kids = pids.()
is_pid(kids[WorkerA]) and is_pid(kids[WorkerB]) and kids[WorkerA] != before[WorkerA] and kids[WorkerB] != before[WorkerB]
end, 200)
after_map = pids.()
IO.inspect([after_map[WorkerA] != before[WorkerA], after_map[WorkerB] != before[WorkerB]])
IO.inspect(GenServer.call(after_map[WorkerA], :get))
IO.inspect(GenServer.call(after_map[WorkerB], :get))
Supervisor.stop(sup)Key points
- One for one: only the crashed child restarts, and the others keep running.
- One for all: every child restarts when any single child crashes.
- Rest for one: the crashed child and every child started after it restart.
- Order: children start in list order, so rest for one depends on position in the list.
- Values: after a restart, a worker answers with its starting value again.
In this example, a rest for one supervisor starts two workers, the first worker crashes, both changed flags are true because the second worker started after the first, and both workers answer with their starting values again.
Note
a supervisor restarts children on its own, without a caller asking for each restart.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
A rest for one supervisor has workers A, B, and C in order. Worker B crashes. Which workers restart?