🎓 A1Learner
🔍
0 of 46 lessons completed

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?