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


Buy Me a Coffee

Twitter / Github / Email