handle_call 中发生的陷阱进程崩溃

Trap process crash which occurs in handle_call

我有 2 个 GenServer 模块 - A 和 B。B 监视 A 并实施 handle_info 以在 A 崩溃时接收 :DOWN 消息。

在我的示例代码中,B 向 A 发出同步请求 (handle_call)。在处理请求时,A 崩溃了。 B 应该收到 :DOWN 消息,但它没有。为什么?

当我用handle_cast替换handle_call时,B收到了:DOWN消息。你能告诉我为什么 handle_call 不起作用而 handle_cast 起作用吗?

这是简单的示例代码:

defmodule A do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, :ok, name: :A)
  end

  def fun(fun_loving_person) do
    GenServer.call(fun_loving_person, :have_fun)
  end

  def init(:ok) do
    {:ok, %{}}
  end

  def handle_call(:have_fun, _from, state) do
    ######################### Raise an error to kill process :A
    raise "TooMuchFun"

    {:reply, :ok, state}
  end
end

defmodule B do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, :ok, name: :B)
  end

  def spread_fun(fun_seeker) do
    GenServer.call(:B, {:spread_fun, fun_seeker})
  end

  def init(:ok) do
    {:ok, %{}}
  end

  def handle_call({:spread_fun, fun_seeker}, _from, state) do
    ######################### Monitor :A
    Process.monitor(Process.whereis(:A))

    result = A.fun(fun_seeker)
    {:reply, result, state}
  rescue
    _ -> IO.puts "Too much fun rescued"
    {:reply, :error, state}
  end

  ######################### Receive :DOWN message because I monitor :A
  def handle_info({:DOWN, _ref, :process, _pid, _reason}, state) do
    IO.puts "============== DOWN DOWN DOWN =============="
    {:noreply, state}
  end
end

try do
  {:ok, a} = A.start_link
  {:ok, _b} = B.start_link
  :ok = B.spread_fun(a)
rescue
  exception -> IO.puts "============= #{inspect exception, pretty: true}"
end

In my example code, B makes synchronous request (handle_call) to A. While processing the request, A crashes. B is supposed to receive :DOWN message but it doesn't. Why?

B 在 A 崩溃时确实收到了 :DOWN 消息,但是因为您仍在调用 A 的处理程序中,所以它没有机会处理 :DOWN 消息,直到handle_call 回调完成。但是它永远不会完成,因为调用将因退出而失败,这也会使 B 崩溃。

When I replaced handle_call with handle_cast, B received :DOWN message. Can you please tell me why handle_call doesn't work whereas handle_cast does?

调用是同步的,转换是异步的,所以在这种情况下,转换到 A 的 handle_call 回调完成,然后 B 可以自由处理 :DOWN 消息。 B 不会崩溃,因为强制转换会隐式忽略尝试发送消息时的任何失败,它是 "fire and forget".

在我看来,您正在尝试处理调用 A 时崩溃的问题,这很简单,就像这样:

def handle_call({:spread_fun, fun_seeker}, _from, state) do
  ######################### Monitor :A
  Process.monitor(Process.whereis(:A))

  result = A.fun(fun_seeker)
  {:reply, result, state}
catch
  :exit, reason ->
    {:reply, {:error, reason}, state}
rescue
  _ -> 
   IO.puts "Too much fun rescued"
   {:reply, :error, state}
end

这将捕获远程进程不活动、死亡或超时时发生的退出。您可以通过在 catch 子句中指定要防止的原因来匹配特定的退出原因,例如 :noproc

我不清楚你是否需要显示器,我想这取决于你想用它做什么,但在你给出的例子中我会说你不需要。