Help Getting Supervisor to Restart GenServer

I’m a complete beginner to Elixir and I’m having a problem that I’m hoping is quite obvious to a more experienced eye. I’m working through the OTP chapter of Chris McCord’s Programming Phoenix (the beta). There is an exercise which has you set up a supervised GenServer to maintain a countdown, but I’ve been having trouble getting my Supervisor to restart my Counter.

My application.ex looks like:

defmodule InfoSys.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  def start(_type, _args) do
    # List all child processes to be supervised
    children = [
      # Starts a worker by calling: InfoSys.Worker.start_link(arg)
      {InfoSys.Counter, 3}
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: InfoSys.InfoSupervisor]
    Supervisor.start_link(children, opts)
  end
end

and my counter.ex looks like:

defmodule InfoSys.Counter do
  use GenServer

  def inc(pid), do: GenServer.cast(pid, :inc)

  def dec(pid), do: GenServer.cast(pid, :dec)

  def val(pid), do: GenServer.call(pid, :val)

  def start_link(initial_val) do
    GenServer.start(__MODULE__, initial_val)
  end

  def init(initial_val) do
    Process.send_after(self(), :tick, 1000)
    {:ok, initial_val}
  end

  def handle_info(:tick, val) when val <= 0, do: raise "boom!"

  def handle_info(:tick, val) do
    IO.puts("tick #{val}")
    Process.send_after(self(), :tick, 1000)
    {:noreply, val - 1}
  end

  def handle_cast(:inc, val) do
    {:noreply, val + 1}
  end

  def handle_cast(:dec, val) do
    {:noreply, val - 1}
  end

  def handle_call(:val, _from, val) do
    {:reply, val, val}
  end
end

When I run my application in iex (with iex -S mix), it counts down from 5 and raises, but the Supervisor does not restart the Counter. When I look at my supervision tree in the observer, my counter never appears at all (though it is clearly running). Here is the stacktrace:

Interactive Elixir (1.7.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> tick 3
tick 2
tick 1
[error] GenServer #PID<0.131.0> terminating
** (RuntimeError) boom!
    (info_sys) lib/info_sys/counter.ex:19: InfoSys.Counter.handle_info/2
    (stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:711: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: :tick
State: 0

I am on Elixir 1.7.4 and running this application from underneath an Umbrella project. I’m happy to post more project details if it appears they might be useful. Does anyone have any ideas why my Counter isn’t restarting?

The reason is that you are starting the GenSever using GenServer.start/3. This does start the server but does not create the link which the supervisor needs so when the server dies the supervisor does not find out about it. Supervisors do not explicitly link to their children but require the start function they call to do it.

You need to call GenServer.start_link/3.

This is why the start function in the InfoSys.Counter module is called start_link. In some cases you will not want to start the server with the link and then you typically have a start function called start which does this. This is a convention.

11 Likes

Just to add a bit of details: we usually use start when testing using the Elixir shell. This is to avoid having the shell terminate if our started GenServer dies unexpectedly.

Both Erlang and Elixir shells are under a supervisor themselves. If it does terminate their supervisor would restart a new shell.

4 Likes

Thank you both for the experienced eyes! This does indeed solve my problem as expected. I appreciate the help

1 Like