Reputation: 9878
I'm trying to get my head around working with a GenServer
in Phoenix to accumulate information received from different clients to a web socket. The GenServer
looks like this:
defmodule HelloWeb.Stack do
use GenServer
@name {:global, __MODULE__} # this seems to help me to prevent having to drag the pid everywhere in the application
# Client
def start_link do
#GenServer.start_link(__MODULE__, [])
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def add(item) do
GenServer.cast(@name, item)
end
def view do
GenServer.call(@name, :view)
end
def remove(item) do
GenServer.cast(@name, {:remove, item})
end
def wipe do
GenServer.cast(@name, :wipe)
end
#Server
def init(list) do
{:ok, list}
end
def handle_cast(:wipe, _list) do
updated_list = []
{:noreply, updated_list}
end
def handle_cast({:remove, item}, list) do
updated_list = Enum.reject(list, fn(i) -> i == item end)
{:noreply, updated_list}
end
def handle_cast(item, list) do
updated_list = [item|list]
{:noreply, updated_list}
end
def handle_call(:view, _from, list) do
{:reply, list, list}
end
end
This is the relevant part of my Phoenix channel:
def handle_in("answer", payload, socket) do
HelloWeb.Stack.add(payload)
{:noreply, socket}
end
def handle_in("answers", _payload, socket) do
{:reply, {:ok, HelloWeb.Stack.view}, socket}
end
The weird thing is that "answer"
seems to work every time I call it but "answers"
always crashes the GenServer:
[error] GenServer #PID<0.10032.0> terminating
** (stop) exited in: GenServer.call({:global, HelloWeb.Stack}, :view, 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
I'm not sure how to share one GenServer between multiple channel actions. If I use iex
all of the functions work exactly as I expect them to work, but in Phoenix the initialization and usage are spread.
I try to start the GenServer in Application as follows:
defmodule Hello.Application do
use Application
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
import Supervisor.Spec
# Define workers and child supervisors to be supervised
children = [
# Start the endpoint when the application starts
supervisor(HelloWeb.Endpoint, []),
# Start your own worker by calling: Hello.Worker.start_link(arg1, arg2, arg3)
worker(HelloWeb.Stack, []),
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Hello.Supervisor]
Supervisor.start_link(children, opts)
end
Sadly I have trouble debugging into the system and I can't figure out yet how to do that right. Lots of new things to absorb at once and I'm still a few a-ha's away from Elixir enlightenment. I have added two debug
endpoints to debug things in very crude way:
def handle_in("debug2", _payload, socket) do
{:reply, {:ok, %{ genserver: GenServer.whereis(HelloWeb.Stack) }}, socket}
end
def handle_in("debug1", _payload, socket) do
{:ok, pid} = HelloWeb.Stack.start_link
{:reply, {:ok, %{ genserver: GenServer.whereis(HelloWeb.Stack), pid: :erlang.pid_to_list(pid) }}, socket}
end
debug2
just keeps returning null
for HelloWeb.Stack
, but when I run debug1
the first time I get null
and a valid pid
, the second time it crashes with:
** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.12310.0>}}
This seems to indicate that it starts successfully, binds itself to a unique value and doing that for a second time is rejected because that particular process is already started. However I cannot use HelloWeb.Stack
to reach it. Let me show the other relevant parts of the code after doing some changes and tests:
defmodule HelloWeb.Stack do
use GenServer
@name {:global, __MODULE__}
# Client
def start_link do
#GenServer.start_link(__MODULE__, [])
GenServer.start_link(__MODULE__, [], name: @name)
end
This is how the worker is started in application.ex:
worker(HelloWeb.Stack, []), #how to verify it's really starting something and to what value it stores it's pid?
Upvotes: 4
Views: 5608
Reputation: 2077
HelloWeb.Stack.start_link/1
is not defined and thus cannot be calledYou implement the function start_link/0
. It does not take any arguments. In the docs I usually see start_link/1
being implemented in order to start your GenServer.
As I read the GenServer docs, HelloWeb.Stack.start_link/1
is always invoked when you start your GenServer as part of the supervision tree. In your case HelloWeb.Stack.start_link/1
is not defined and thus cannot be called.
So I suggest you implement start_link/1
like:
# Client
def start_link(_init_args) do
#GenServer.start_link(__MODULE__, [])
GenServer.start_link(__MODULE__, [], name: HelloWeb.Stack)
end
What happens internally is something the Elixir/Erlang experts have to answer (Edits are very welcome!), but I think this is the root of your problem.
Upvotes: 2
Reputation: 2961
Sounds like you aren't starting the GenServer with the application and therefore, the message is correct - e.g. there is no process alive.
The reason the HelloWeb.Stack.add(payload)
function still works is because that's the intention of the cast functionality of GenServer's, e.g. you cast to the process because you don't care about the returned result at and whether it succeeds or fails (otherwise you would use call
)
For example:
iex(1)> GenServer.cast(:non_existing_genserver, :add)
:ok
Here you can see that even though the GenServer doesn't exist, cast
still returns an ack message, but in reality it's not going to go anywhere.
To solve your problem, navigate to your application.ex
file and add in a worker to start the GenServer when the application starts up.
children = [
#...other workers/supervisors
{HelloWeb, []}
]
Upvotes: 2