Adding supervision

Published October 21, 2018 by Toran Billups

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)
      def init(:ok) do
        children = [EX.Worker]
        Supervisor.init(children, strategy: :one_for_one)

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
      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

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)

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", "")
    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.

Twitter / Github / Email