Converting deprecated :simple_one_for_one to DynamicSupervisor

supervisor
dynamicsupervisor
#1

Background

I am reading the “Functional Web Development with Elixir, OTP and Phoenix” book, and I finished a Supervisor that supervises Games. Games can start and end so this is in reality a DynamicSupervisor, but when the book was written the strategy :simple_one_for_one was still not deprecated and so that is what they used.

My objective is to replace the deprecated Supervisor with a Dynamic one and get rid of the deprecation warnings.

Code

Following is the (deprecated) Supervisor the book gives (I added specs):

defmodule IslandsEngine.GameSupervisor do
  use Supervisor

  alias IslandsEngine.Game

  @spec start_link(any) :: Supervisor.on_start
  def start_link(_args), do:
    Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)

  @spec start_game(String.t) :: Supervisor.on_start_child
  def start_game(name), do:
    Supervisor.start_child(__MODULE__, [name])

  @spec stop_game(String.t) :: :ok | {:error, :not_found | :simple_one_for_one}
  def stop_game(name) do
    :ets.delete(:game_state, name)
    Supervisor.terminate_child(__MODULE__, pid_from_name(name))
  end

  @impl Supervisor
  @spec init(:ok) :: {:ok, tuple}
  def init(:ok), do:
    Supervisor.init([Game], strategy: :simple_one_for_one)

  defp pid_from_name(name) do
    name
    |> Game.via_tuple()
    |> GenServer.whereis()
  end
end

This code works, but makes Dyalizer go crazy. Furthermore, the strategy used here is also deprecated.

This is my attempt at upgrading this code:

defmodule IslandsEngine.GameSupervisor do
  use DynamicSupervisor

  alias IslandsEngine.Game

  @spec start_link(any) :: DynamicSupervisor.on_start
  def start_link(_args), do:
    DynamicSupervisor.start_link(__MODULE__, :ok, name: __MODULE__)

  @spec start_game(String.t) :: DynamicSupervisor.on_start_child
  def start_game(name), do:
    DynamicSupervisor.start_child(__MODULE__, {Game, [name]})

  @spec stop_game(String.t) :: :ok | {:error, :not_found}
  def stop_game(name) do
    :ets.delete(:game_state, name)
    DynamicSupervisor.terminate_child(__MODULE__, pid_from_name(name))
  end

  @impl DynamicSupervisor
  @spec init(:ok) :: {:ok, DynamicSupervisor.sup_flags}
  def init(:ok), do:
    DynamicSupervisor.init(strategy: :one_for_one)

  defp pid_from_name(name) do
    name
    |> Game.via_tuple()
    |> GenServer.whereis()
  end

end

Problem

However, when I run my version of the DynamicSupervisor I get the following error:

IslandsEngine.GameSupervisor.start_game("Fred") 
{:error,
 {:undef,
  [
    {IslandsEngine.Game, :start_link, [], []},
    {DynamicSupervisor, :start_child, 3,
     [file: 'lib/dynamic_supervisor.ex', line: 690]},
    {DynamicSupervisor, :handle_start_child, 2,
     [file: 'lib/dynamic_supervisor.ex', line: 676]},
    {:gen_server, :try_handle_call, 4, [file: 'gen_server.erl', line: 661]},
    {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 690]},
    {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 249]}
  ]}}

The deprecated version works just fine.

Questions

What am I doing wrong? Why are the two Supervisors not equivalent?

#2

From the stacktrace you can see that Game.start_link/0 is being called. But why start_link with arity 0? The default should be to call start_link(game_name), which is what you expect, UNLESS there is a child_spec/1 function in the Game module (or an argument on use GenServer) that is instructing it to pass no arguments to Game.start_link.

Once you remove the custom child_spec, everything should work.

#3

Indeed, according to the book this is the case. The thing I don’t understand is why the deprecated version works without changing the Game.child_spec but the DynamicSupervisor version doesn’t.

Are these 2 versions incompatible in a way I am missing here?