citruz
Hi,
I am trying migrate a small game I wrote to LiveView. However, I am stuck since multiple hours and can’t figure out why the GenServer processes I create seem to die instantly.
I have an Agent module called Global which maintains a Map of Game (GenServer) processes:
defmodule Fakeartist.Global do
use Agent
...
def games do
Agent.get(__MODULE__, &(&1))
end
def new_game(player_name, player_id, num_rounds) do
...
{:ok, game} = Game.start_link(player_name, player_id, num_rounds)
Agent.update(__MODULE__, &Map.put_new(&1, token, game))
{:ok, token, game}
end
end
Currently, without LIveView, I do this to create a game inside a controller:
defmodule FakeartistWeb.GameController do
...
def create(conn, %{"user" => %{"num_rounds" => num_rounds}}) do
...
{:ok, token, _} = Global.new_game(username, get_session(conn, :user_id), num_rounds)
conn
|> redirect(to: Routes.game_path(conn, :show, token))
end
end
It works perfectly, I can retrieve the Game process, call functions on it and so in.
Now I tried to implement the same with LiveViews:
index.html.leex:
<button phx-click="addgame">Create</button>
...
<%= for {token, game} <- @games do %>
<td><%= token %></td>
<td><%= length(Game.get_players(game)) %></td>
<% end %>
index.ex:
defmodule FakeartistWeb.GameLive.Index do
...
@impl true
def mount(_params, _session, socket) do
socket = socket
|> assign(:games, fetch_games())
{:ok, socket}
end
@impl true
def handle_event("addgame", params, socket) do
IO.puts("addgame: #{inspect params}")
{:ok, token, game} = Global.new_game("some_username", "some_user_id", 2)
IO.puts("addgame: #{inspect Game.props(game)}")
IO.puts("addgame: #{inspect Global.games[token]}")
socket = socket
|> assign(:games, fetch_games())
|> push_redirect(to: "/livegame")
{:noreply, socket}
end
defp fetch_games do
Global.games()
end
As soon as I click the button, the new Game process is created which can observed in the log:
addgame: %{<...>}
addgame: %{category: :none, current_player: :none, <...>}
addgame: #PID<0.473.0>
This shows the pid and that I am able to call methods on it. However, as soon as the view tries to display the game list, it crashes:
** (stop) exited in: GenServer.call(#PID<0.473.0>, :get_players, 5000)
** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
The game was added to the map in Global, but the process that the map entry is referring to and which was still running in the handle_event call seems to have died now.
I have no clue why, is there something I am missing?
I appreciate any help. I tried to reduce the code as much as possible, hope its understandable.
Thanks
Felix
Trending in Questions
Other Trending 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
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










First 6 of 6 Posts
mindok
Have you tried debugging the lifecycle of the game process - e.g. using
:sys.trace/3(see GenServer — Elixir v1.20.2) or implementing theterminatecallback?citruz
I already tried the latter, adding a
terminatecallback with a debug message, but it was not called.The same seems to be true for
:sys.trace/3- I can see thepropscall but nothing more:I am new to elixir, am I missing something fundamental about the lifecycle or scope of processes? Are LiveViews in any way different to regular view controllers?
The full code is here https://github.com/citruz/fakeartist, in case that helps.
ityonemo
You’re missing a whole lot about the lifecycle of processes. For starters, you’re calling Game.start_link inside your liveview, which is unusual. Typically start_link should be called by supervisors, you’ll want your games to be supervised, as supervisors are process lifecycle managers. As your code stands, the lifecycle of your game is directly tied to the lifecycle of your liveview (hence the _link). If your lv dies, so does your game.
Also instead of an agent, you probably want to use a registry to keep track of processes with ids that are meaningful (aren’t pids).
citruz
Thanks, I was suspecting something like that. Please help me understand what is wrong with my current approach.
I added a Supervisor child in application.ex:
I though that this would create a “global singleton” of my
GlobalAgent which would persist throughout the whole lifetime of the application. By usingGame.start_linkinGlobal.new_gameI assumed that the lifetime of theGameprocess would be bound to theGlobalprocess and thus to the application.What did I get wrong?
citruz
Ah, I see.
Game.start_linkis called insideGlobal.new_gamebut is not called in the context of that process and thus theGameprocess was bound to the wrong parent.I refactored
Globalinto a namedGenServerand not its workingNext, I will try to understand the concept of registries… What is the problem with passing around pids?
mindok
No problem with passing round pids per se, but using a registry to lookup processes based on a key that has meaning to your application is so much easier. I found the initial setup a bit of a pain tbh, but in use it makes the code a lot easier to follow. Here’s the “recipe” I ended up with. I’m still missing a couple of pieces - for some reason my registered processes keep running even though the
start_linkfor each “game” is called from a liveview process, but I think I should really have another level of supervision in there as per the supervision tree towards the end of The Erlangelist - To spawn, or not to spawn?.Anyway…
via_tuplefunction that packages your meaningful key into a token that Registry can use to lookup processes. You will need the same name you used in step 1. You can call the function whatever you want, but everyone seems to usevia_tuple