2017-08-28 3 views
1

2つのGenServerモジュール - AとBがあります。BはAを監視して、Aがクラッシュしたときに:DOWNメッセージを受信するようにhandle_infoを実装しています。handle_callで発生するトラッププロセスクラッシュ

私のコード例では、BはAに同期要求(handle_call)を行います。要求の処理中にAがクラッシュします。 Bは:DOWNメッセージを受信するはずですが、そうではありません。どうして?

handle_callhandle_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はAに同期要求(handle_call)を行います。 BはDOWNメッセージを受信するはずですが、DOWNメッセージは受信しません。どうして?

ときにクラッシュBは:DOWNメッセージを受信んが、あなたはAへの呼び出しのためのハンドラに残っているので、handle_callコールバックが完了するまで、それは:DOWNメッセージを処理する機会がありません。しかし、それでも完了することはありません。コールが出口で失敗するため、Bもクラッシュします。

handle_callをhandle_castに置き換えたとき、BはDOWNメッセージを受信しました。 handle_callがなぜhandle_castが動作しないのに対し、なぜ動作しないのか教えてください。

コールが同期され、キャストは非同期であるので、この場合には、Aにキャストhandle_callコールバックが完了し、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

ありがとうございました!素晴らしい説明。 別のモジュール(A)に依存するRabbitMQメッセージコンシューマ(モジュールB)があります。 Aがクラッシュし、Bがメッセージを拒否して再キューできないときに、メッセージがリムボウになることは望ましくありません。あなたはもっと良いアプローチを知っていますか? –

+1

1つの方法は、Aが失敗した場合にBにメッセージをドロップさせ、メッセージが期限切れになり、RabbitMQによって自動的に再キューされるようにすることです(キューが適切に構成されていると仮定します)。もう1つは、上記の「キャッチ」戦術を使用して、メッセージを数回再試行してからやめてください。さらに別の方法は、Bがそのメッセージの処理を担当するプロセスにメッセージを渡し、Bを解放して他のメッセージを消費し続け、成功するまでそのプロセスを再試行させることです。しかし、メッセージの順序が重要でない場合にのみ動作し、次にフロー制御の問題があります。 – bitwalker

+1

私の意見では、Bを拒否/再キューする方法を見つけ出す方が良いでしょう。あなたは出口を捕まえています)、または失敗したメッセージをドロップして、自動的にメッセージを期限切れにして再キューに入れることができます。メッセージの順序が重要な場合は、再試行がより良い選択となる可能性があります。 – bitwalker

関連する問題