Elixir Supervisor Shutdown
Supervisor shutdown is the orderly end of a supervisor and its children. Each child gets a chance to clean up before its process ends.
Stopping a worker runs cleanup with a normal reason
A worker ends through a normal stop, and its cleanup message carries the normal reason. The parent prints that reason.
defmodule TWorker do
use GenServer
def start_link(parent), do: GenServer.start_link(__MODULE__, parent)
def init(parent), do: {:ok, parent}
def handle_call(:get, _from, parent), do: {:reply, :ok, parent}
def terminate(reason, parent) do
send(parent, {:ended, reason})
:ok
end
end
{:ok, pid} = TWorker.start_link(self())
GenServer.stop(pid)
receive do
{:ended, reason} -> IO.inspect(reason)
endA supervisor stops children in reverse order with a shutdown reason
Both workers trap exits, so both run cleanup. The second started ends first, and both reasons are shutdown.
defmodule OrdA do
use GenServer
def start_link({id, parent}), do: GenServer.start_link(__MODULE__, {id, parent})
def init({id, parent}) do
Process.flag(:trap_exit, true)
{:ok, {id, parent}}
end
def handle_call(:get, _from, s), do: {:reply, :ok, s}
def terminate(reason, {id, parent}) do
send(parent, {:ended, id, reason})
:ok
end
end
defmodule OrdB do
use GenServer
def start_link({id, parent}), do: GenServer.start_link(__MODULE__, {id, parent})
def init({id, parent}) do
Process.flag(:trap_exit, true)
{:ok, {id, parent}}
end
def handle_call(:get, _from, s), do: {:reply, :ok, s}
def terminate(reason, {id, parent}) do
send(parent, {:ended, id, reason})
:ok
end
end
defmodule SupO do
use Supervisor
def start_link(parent), do: Supervisor.start_link(__MODULE__, parent)
def init(parent) do
Supervisor.init([{OrdA, {"first", parent}}, {OrdB, {"second", parent}}], strategy: :one_for_one)
end
end
{:ok, sup} = SupO.start_link(self())
Supervisor.stop(sup)
order = for _ <- 1..2 do
receive do
{:ended, id, reason} -> {id, reason}
end
end
IO.inspect(order)A brutal kill child skips cleanup
A trapping worker is marked for a brutal kill. The supervisor stops, no cleanup message arrives, and the worker is gone.
defmodule Brut do
use GenServer
def start_link(parent), do: GenServer.start_link(__MODULE__, parent)
def init(parent) do
Process.flag(:trap_exit, true)
{:ok, parent}
end
def handle_call(:get, _from, s), do: {:reply, :ok, s}
def terminate(reason, parent) do
send(parent, {:ended, reason})
:ok
end
end
defmodule SupBrut do
use Supervisor
def start_link(parent), do: Supervisor.start_link(__MODULE__, parent)
def init(parent) do
children = [%{id: Brut, start: {Brut, :start_link, [parent]}, shutdown: :brutal_kill}]
Supervisor.init(children, strategy: :one_for_one)
end
end
{:ok, sup} = SupBrut.start_link(self())
child = sup |> Supervisor.which_children() |> hd() |> elem(1)
Supervisor.stop(sup)
result = receive do
{:ended, reason} -> reason
after
500 -> :no_message
end
IO.inspect(result)
IO.inspect(Process.alive?(child))Example
defmodule TrapSaver do
use GenServer
def start_link(parent), do: GenServer.start_link(__MODULE__, parent)
def init(parent) do
Process.flag(:trap_exit, true)
{:ok, {parent, "draft"}}
end
def handle_call(:get, _from, s), do: {:reply, :ok, s}
def terminate(_reason, {parent, value}) do
send(parent, {:saved, value})
:ok
end
end
defmodule Plain do
use GenServer
def start_link(parent), do: GenServer.start_link(__MODULE__, parent)
def init(parent), do: {:ok, parent}
def handle_call(:get, _from, s), do: {:reply, :ok, s}
def terminate(_reason, parent) do
send(parent, {:saved, "plain"})
:ok
end
end
defmodule SupEx do
use Supervisor
def start_link(parent), do: Supervisor.start_link(__MODULE__, parent)
def init(parent) do
Supervisor.init([{TrapSaver, parent}, {Plain, parent}], strategy: :one_for_one)
end
end
{:ok, sup} = SupEx.start_link(self())
Supervisor.stop(sup)
saved = receive do
{:saved, value} -> value
end
IO.inspect(saved)
other = receive do
{:saved, value} -> value
after
500 -> :no_message
end
IO.inspect(other)Key points
- Cleanup: a terminate callback runs cleanup work when a worker ends.
- Normal stop: stopping a worker on its own runs cleanup with a normal reason.
- Shutdown reason: stopping a supervisor runs cleanup with a shutdown reason, for workers that trap exits.
- Trap: trapping exits turns an exit signal into a cleanup chance.
- Reverse order: a supervisor stops its children in reverse start order.
- Kill: killing a worker never runs its cleanup.
In this example, a supervisor starts a trapping worker and a plain worker, a stop ends the plain worker first without cleanup, the trapping worker saves its draft, the parent sees the draft, and a second wait finds no message from the plain worker.
Note
a brutal kill child skips cleanup entirely, even when it traps exits.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
A supervisor stops. Which worker runs its cleanup?