Fl4m3Ph03n1x

Fl4m3Ph03n1x

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?

Showing Posts 1 to 8

josevalim

josevalim

Creator of Elixir

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.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

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?

Chrichton

Chrichton

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.

sotoseattle

sotoseattle

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
rm-rf-etc

rm-rf-etc

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.

Fl4m3Ph03n1x

Fl4m3Ph03n1x OP

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.

kigila

kigila

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.

kigila

kigila

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

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
psy-q
I’m trying to set up Emacs with elixir-ls via lsp-mode and credo via Flycheck. This should mostly be preconfigured as Flycheck picks up c...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews