2017-08-28 66 views
1

我有2個GenServer模塊 - A和B. B監視A並執行handle_info以在A崩潰時接收:DOWN消息。在handle_call中發生陷阱進程崩潰

在我的示例代碼中,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 

回答

3

在我的示例代碼,B使同步請求(handle_call)至A.在處理請求,A崩潰。 B應該接收:DOWN消息,但它不。爲什麼?

B則收到:DOWN消息時崩潰,而是因爲你仍然在處理程序調用A,它不會有機會,直到handle_call回調完成處理:DOWN消息。它不會完成,因爲調用會失敗並退出,這也會導致B崩潰。

當我用handle_cast替換handle_call時,B收到:DOWN消息。你能告訴我爲什麼handle_call不起作用,而handle_cast呢?

調用是同步的,鑄件是異步的,所以在這種情況下,回調handle_call其施放到A完成,和B就成爲空閒處理:DOWN消息。 B不會崩潰,因爲演員隱含地忽略了嘗試發送該消息的任何失敗,這是「火併且忘記」。

在我看來,你想打電話給它,當處理一個崩潰,就是平凡的完成,像這樣:

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條款中指定原因。

我不清楚你是否需要顯示器,我想這取決於你想要使用它,但在你給出的例子中,我會說你不知道。

+0

非常感謝!真棒解釋。 我有一個RabbitMQ消息使用者(模塊B),依賴於另一個模塊(A)。當A崩潰並且B不能拒絕並重新發送消息時,我不希望消息陷入僵局。你知道更好的方法嗎? –

+1

那麼一種方法是讓B在A失敗時丟棄消息,並讓消息過期並自動重新由RabbitMQ重新發送消息(假設隊列已正確配置)。另一種方法是使用上面的「捕捉」策略,在放棄之前重試該信息幾次。另一種方法是讓B將消息傳遞給負責處理該消息的進程,釋放B繼續消費其他消息並讓該進程重試直至成功爲止;但這隻適用於消息順序不重要的情況,然後你有一個流量控制問題來處理 – bitwalker

+1

在我看來,你最好是找到一種方法讓B拒絕/重新發送消息(這當然應該是可行的,如果你正在捕捉退出),或者丟棄失敗的消息,並讓它們自動失效並重新排序。這實際上取決於你需要提供什麼保證,如果消息的順序很重要,重試可能是一個更好的選擇。 – bitwalker