Converting deprecated :simple_one_for_one to DynamicSupervisor

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 Likes

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.

2 Likes

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?

1 Like

Unfortunately, the solution did not work for me. I removed the child_spec in use GenServer,
tried: IslandsEngine.GameSupervisor.start_game(“Fred”)
and got:

{:function_clause,
[
{IslandsEngine.Game, :start_link, [[“Fred”]],
[file: ‘lib/islands_engine/game.ex’, line: 27]},
{DynamicSupervisor, :start_child, 3,
[file: ‘lib/dynamic_supervisor.ex’, line: 692]},
{DynamicSupervisor, :handle_start_child, 2,
[file: ‘lib/dynamic_supervisor.ex’, line: 678]},
{:gen_server, :try_handle_call, 4, [file: ‘gen_server.erl’, line: 706]},
{:gen_server, :handle_msg, 6, [file: ‘gen_server.erl’, line: 735]},
{:proc_lib, :init_p_do_apply, 3, [file: ‘proc_lib.erl’, line: 226]}
]}}

I changed:
def start_game(name), do: DynamicSupervisor.start_child(MODULE, {Game, [name]})
to:
def start_game(name), do: DynamicSupervisor.start_child(MODULE, {Game, name})

and now everything works.

3 Likes

I had the same issue. The thing that did the trick for me was how you define the spec:

spec = %{id: Game, start: {Game, :start_link, [name]}}

So the whole GameSupervisor looks like this:

defmodule IslandsEngine.GameSupervisor do
  use DynamicSupervisor

  alias IslandsEngine.Game

  def start_link(_options) do
    IO.puts("starting the game supervisor...")
    DynamicSupervisor.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  def start_game(name) do
    spec = %{id: Game, start: {Game, :start_link, [name]}}
    DynamicSupervisor.start_child(__MODULE__, spec)
  end

  def stop_game(name) do
    DynamicSupervisor.terminate_child(__MODULE__, pid_from_name(name))
  end

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

  def init(_args) do
    DynamicSupervisor.init(
      strategy: :one_for_one)
  end
end
4 Likes

This code doesn’t make sense to me, start_game doesn’t call pid_from_name, but stop_game does. It doesn’t look like the via_tuple is associated with the process at startup.

Reading the book will help understand it. I recommend you have a look at the list of discounts the forum offers:

There might be other threads, I recommend you search for a few.

Hello, did you manage to sold the issue ?
I solved the start_game method, but the stop_game method is broken. pid_from_name method return nil always. Thats suckes considering I have been religiously following the book. I believe it is some problem with Registry but I have no clue what is wrong. Man I wish I had a ginie. ChatGPT says it cannot help.

:smiling_face_with_tear: :smiling_face_with_tear: :smiling_face_with_tear: → Its funny and sad that after 5 hours I have the solution. I hope nobody else reading this book will face the same hardship I had to go throw. man, thats crazy. Here is the solution:

In the application module include the GameSupervisor to the supervision tree like this:


{DynamicSupervisor, name: IslandEngine.GameSupervisor}

In the game.ex module your GenServer.start_link should be called like this:

def start_game(name) when is_binary(name), do: GenServer.start_link(MODULE, [name], name: via_tuple(name))

you can see that I wrap mine in a public method I called start_game( it beautiful this way)

And finally in the GameSupervisor module, here is how I defined the start_game module:

def start_game(name) do
   child_spec = %{id: Game, start: {Game, :start_game, [name]}}
   DynamicSupervisor.start_child(__MODULE__, child_spec)
end

Maybe I have a ginie and dont know it. Hope that helps you. No soffer no more.

1 Like