在凤凰城使用GenServer - 进程不活跃

时间:2018-02-20 02:03:27

标签: elixir phoenix-framework gen-server

我正试图与凤凰城的GenServer合作,将从不同客户端收到的信息累积到网络套接字中。 GenServer看起来像这样:

 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

这是我凤凰频道的相关部分:

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

奇怪的是"answer"似乎每次调用时都有效,但"answers"总是崩溃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

我不确定如何在多个频道操作之间共享一个GenServer。如果我使用iex所有函数的工作方式与我期望的完全相同,但在Phoenix中,初始化和使用会被传播。

我尝试在应用程序中启动GenServer,如下所示:

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

可悲的是,我在调试系统时遇到了麻烦,我无法弄清楚如何做到这一点。很多新东西一下子吸收,我距离Elixir的启蒙还有几个远的地方。我添加了两个debug端点以非常粗略的方式调试事物:

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只是继续null HelloWeb.Stack,但是当我第一次获得debug1null pid时,第二次崩溃:

** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.12310.0>}}

这似乎表明它成功启动,将自身绑定到一个唯一值,并且第二次这样做会被拒绝,因为该特定进程已经启动。但是,我无法使用HelloWeb.Stack来覆盖它。在进行一些更改和测试之后,让我展示代码的其他相关部分:

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

这是在application.ex中启动worker的方式:

worker(HelloWeb.Stack, []), #how to verify it's really starting something and to what value it stores it's pid?

2 个答案:

答案 0 :(得分:0)

听起来你没有使用应用程序启动GenServer,因此,消息是正确的 - 例如没有过程活着。

HelloWeb.Stack.add(payload)功能仍然有效的原因是因为这是GenServer的强制转换功能的意图,例如:你投入到这个过程是因为你不关心返回的结果,以及它是成功还是失败(否则你会使用call

例如:

iex(1)> GenServer.cast(:non_existing_genserver, :add)
:ok

在这里你可以看到,即使GenServer不存在,cast仍然会返回一条确认消息,但实际上它不会去任何地方。

要解决您的问题,请导航到您的application.ex文件并添加一个工作人员以在应用程序启动时启动GenServer。

children = [
  #...other workers/supervisors
  {HelloWeb, []}
]

答案 1 :(得分:0)

HelloWeb.Stack.start_link/1未定义,因此无法调用

您实现功能start_link/0。它不需要任何参数。在文档中,我通常会看到start_link/1的实现是为了启动您的GenServer。

当我阅读GenServer docs时,在启动GenServer作为监督树的一部分时,总是会调用HelloWeb.Stack.start_link/1。在您的情况下,HelloWeb.Stack.start_link/1未定义,因此无法调用。

所以我建议您像这样实现start_link/1

# Client
def start_link(_init_args) do
  #GenServer.start_link(__MODULE__, [])
  GenServer.start_link(__MODULE__, [], name: HelloWeb.Stack)
end

内部发生的事情是Elixir / Erlang专家必须回答的问题(非常欢迎编辑!),但是我认为这是问题的根源。