Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a GenServer in Phoenix - the process is not alive

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?
like image 481
Lucas van Dongen Avatar asked Oct 25 '25 23:10

Lucas van Dongen


1 Answers

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, []}
]
like image 162
Harrison Lucas Avatar answered Oct 28 '25 23:10

Harrison Lucas