在handle_call中发生的陷阱进程崩溃

时间:2017-08-28 22:09:34

标签: elixir otp

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

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

当我用handle_call替换handle_cast时,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

1 个答案:

答案 0 :(得分:3)

  

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

当A崩溃时,

B确实会收到:DOWN消息,但由于您仍然处于对A的呼叫的处理程序中,因此它不会有机会处理:DOWN消息,直到handle_call回调完成。虽然它不会完成,因为呼叫将以退出失败,这也会使B崩溃。

  

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

调用是同步的,强制转换是异步的,因此在这种情况下,转换为A的handle_call回调完成,然后B可以自由处理:DOWN消息。 B不会崩溃,因为演员隐含地忽略了尝试发送消息的任何失败,它会发射并忘记"。

在我看来,你正试图在打电话时遇到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

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

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