Fl4m3Ph03n1x
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?
Marked As Solved
josevalim
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.
Also Liked
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
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.
kigila
→ 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.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









