简体   繁体   English

在Phoenix中使用GenServer-该过程尚未生效

[英]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. 我正在努力与Phoenix的GenServer合作,将从不同客户端收到的信息累积到Web套接字。 The GenServer looks like this: 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

This is the relevant part of my Phoenix channel: 这是我的Phoenix频道的相关部分:

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: 奇怪的是,每次我调用它时, "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

I'm not sure how to share one GenServer between multiple channel actions. 我不确定如何在多个通道操作之间共享一个GenServer。 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. 如果我使用iex所有功能都可以按我预期的方式正常工作,但是在Phoenix中,初始化和用法已得到广泛推广。

I try to start the GenServer in Application as follows: 我尝试按以下方式在Application中启动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

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. 大量的新事物可以立即吸收,而Elixir的启蒙运动距离我还有几分之遥。 I have added two debug endpoints to debug things in very crude way: 我添加了两个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 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: debug2始终为HelloWeb.Stack返回null ,但是当我第一次运行debug1时,我第一次获得null和一个有效的pid ,第二次崩溃时,它的出现是:

** (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. 但是,我无法使用HelloWeb.Stack来到达它。 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: 这是在application.ex中启动工作程序的方式:

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

Sounds like you aren't starting the GenServer with the application and therefore, the message is correct - eg there is no process alive. 听起来好像您没有使用该应用程序启动GenServer,因此,该消息是正确的-例如,没有活动的进程。

The reason the HelloWeb.Stack.add(payload) function still works is because that's the intention of the cast functionality of GenServer's, eg 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 ) HelloWeb.Stack.add(payload)函数仍然起作用的原因是因为这是GenServer的强制转换功能的目的,例如,您强制转换为该进程是因为您不关心返回的结果是否成功以及是否成功(否则,您将使用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. 在这里,您可以看到,即使GenServer不存在, cast仍会返回一条确认消息,但实际上它不会走到任何地方。

To solve your problem, navigate to your application.ex file and add in a worker to start the GenServer when the application starts up. 要解决您的问题,请导航至application.ex文件,并添加一个工作程序以在应用程序启动时启动GenServer。

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

HelloWeb.Stack.start_link/1 is not defined and thus cannot be called 未定义HelloWeb.Stack.start_link/1 ,因此无法调用

You implement the function start_link/0 . 您实现了功能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. 在文档中,我通常会看到为启动您的GenServer而执行了start_link/1

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. 当我阅读GenServer文档时 ,在启动GenServer作为监管树的一部分时,始终会调用HelloWeb.Stack.start_link/1 In your case HelloWeb.Stack.start_link/1 is not defined and thus cannot be called. 在您的情况下,未定义HelloWeb.Stack.start_link/1 ,因此无法调用它。

So I suggest you implement start_link/1 like: 所以我建议您像这样实现start_link/1

# 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. 内部发生的事情是Elixir / Erlang专家必须回答的问题(非常欢迎编辑!),但是我认为这是问题的根源。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM