In part 3 of the url shortener adventure we add a `Supervisor` to recover from process failure.
Part 3: Supervision
Currently the application has 1 process and if that process blows up for any reason the program will completely die.
pid = GenServer.whereis(EX.Worker)
EX.Worker.get(pid, "x")
Process.exit(pid, :kill)
To make the software more reliable in Elixir we have a primitive that allows us to recover from failures with ease. Start by adding a module called `EX.Supervisor`. This supervisor will monitor the `EX.Worker` process and restart it if any failure is detected. The `:one_for_one` strategy tells Elixir that if a child process dies only that process should be restarted. Other strategies exist that offer a more aggressive restart if that becomes a requirement in your application.
defmodule EX.Supervisor do
use Supervisor
def start_link(opts) do
Supervisor.start_link(__MODULE__, :ok, opts)
end
def init(:ok) do
children = [EX.Worker]
Supervisor.init(children, strategy: :one_for_one)
end
end
To see this module in action we next write a unit test to exercise the Supervisor. You can run this from the command line using `mix test`. Note: I found using `Process.exit` wasn't a good fit for testing this Supervisor (maybe because it's asynchronous). If instead I used `GenServer.stop` the test would wait until the child process (EX.Worker) was truly down.
defmodule EX.SupervisorTest do
use ExUnit.Case, async: true
setup do
start_supervised!(EX.Supervisor)
:ok
end
test "a new child process will restart as needed" do
[{_, pid, _, _}] = Supervisor.which_children(EX.Supervisor)
assert EX.Worker.get(pid, "x") === :undefined
GenServer.stop(pid, :normal)
[{_, new_pid, _, _}] = Supervisor.which_children(EX.Supervisor)
assert new_pid !== pid
assert EX.Worker.get(new_pid, "x") === :undefined
end
end
Before we spin up the application with IEx first open the file `lib/example.ex` and alter the `start` method to use this new Supervisor module.
defmodule EX.Application do
use Application
def start(_type, _args) do
EX.Supervisor.start_link(name: EX.Supervisor)
end
end
To play around with this module in IEx run this command `iex -S mix run`
# pattern match to get the worker pid
[{id, pid, type, module}] = Supervisor.which_children(EX.Supervisor)
EX.Worker.get(pid, "x")
EX.Worker.put(pid, "x", "google.com")
EX.Worker.get(pid, "x")
# kill the worker process
Process.exit(pid, :kill)
# pattern match again. notice the worker pid is different
[{id, pid, type, module}] = Supervisor.which_children(EX.Supervisor)
# note: the worker state was lost
EX.Worker.get(pid, "x")
You can track my progress on github commit by commit. If you just want the code for this post checkout this commit.