2017-08-28 3 views
1

Ich habe 2 GenServer-Module - A und B. B überwacht A und implementiert handle_info, um :DOWN Nachricht zu erhalten, wenn A abstürzt.Trap-Prozessabsturz in handle_call

In meinem Beispiel Code, B macht synchrone Anfrage (handle_call) an A. Während der Bearbeitung der Anfrage stürzt A ab. B soll :DOWN Nachricht empfangen, tut es aber nicht. Warum?

Wenn ich handle_call durch handle_cast ersetzte, erhielt B :DOWN Nachricht. Können Sie mir bitte sagen, warum handle_call nicht funktioniert, während handle_cast tut?

Dies ist die einfache Beispielcode:

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 

Antwort

3

In meinem Beispiel-Code, B synchrone Anforderung (handle_call) A. macht Während der Verarbeitung der Anforderung, A stürzt ab. B soll: DOWN-Nachricht empfangen, tut es aber nicht. Warum?

B empfängt die :DOWN Meldung, wenn ein abstürzt, sondern weil Sie noch in der Behandlungsroutine für den Aufruf von A sind, wird es nicht die Möglichkeit hat, die :DOWN Nachricht zu verarbeiten, bis der handle_call Rückruf abgeschlossen ist. Es wird jedoch niemals abgeschlossen, da der Aufruf mit einem Exit fehlschlägt, was ebenfalls B zum Absturz bringt.

Wenn ich handle_call durch handle_cast ersetzt, erhielt B: DOWN-Nachricht. Können Sie mir bitte sagen, warum handle_call nicht funktioniert, während handle_cast funktioniert?

Anrufe sind synchron, Abgüsse sind asynchron, also in diesem Fall, der der handle_call Rückruf wirft auf A abgeschlossen ist, und B dann frei ist, die :DOWN Nachricht zu verarbeiten. B stürzt nicht ab, weil Umwandlungen implizit jeglichen Fehler beim Versuch, die Nachricht zu senden, ignorieren, es ist "Feuer und Vergessen".

Es scheint mir, Sie mit einem Krachen umgehen versuchen, wenn sie es nennen, das ist trivial, wie dies getan:

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 

Diese Ausfahrt fangen, die beim Auftreten der Remote-Prozess nicht mehr am Leben ist, stirbt oder läuft ab. Sie können bestimmte Ausgangsgründe wie :noproc zuordnen, indem Sie den Grund in der Klausel catch angeben, vor der Sie sich schützen möchten.

Es ist mir nicht klar, dass Sie den Monitor brauchen, ich denke, es hängt davon ab, wofür Sie es verwenden möchten, aber in Ihrem gegebenen Beispiel würde ich sagen, dass Sie es nicht tun.

+0

Vielen Dank! Tolle Erklärung. Ich habe einen RabbitMQ-Nachrichtenkonsumenten (Modul B), der von einem anderen Modul (A) abhängt. Ich möchte nicht, dass Nachrichten in der Schwebe sind, wenn A abstürzt und B die Nachricht nicht ablehnen und zurückfordern kann. Kennen Sie einen besseren Ansatz? –

+1

Nun, ein Ansatz besteht darin, dass B die Nachricht abbricht, wenn A fehlschlägt, und die Nachricht ablaufen lässt und von RabbitMQ automatisch erneut angestellt wird (vorausgesetzt, dass Ihre Warteschlangen entsprechend konfiguriert sind). Eine andere Möglichkeit besteht darin, die "Fang" -Taktik oben zu verwenden und die Nachricht einige Male erneut zu versuchen, bevor Sie aufgeben.Ein weiterer Grund ist, dass B die Nachricht an einen Prozess weitergibt, der für die Verarbeitung dieser Nachricht zuständig ist, wodurch B freigegeben wird, um weitere Nachrichten zu konsumieren, und diesen Prozess wiederholen zu lassen, bis es erfolgreich ist; aber das funktioniert nur, wenn die Reihenfolge der Nachrichten unwichtig ist, und dann haben Sie ein Flow-Control-Problem zu behandeln – bitwalker

+1

Meiner Meinung nach, ist es besser für Sie einen Weg zu finden, B abzulehnen/requeue Nachrichten (die sicherlich machbar sein sollten, wenn Sie können Exits abfangen oder fehlgeschlagene Nachrichten löschen und ablaufen lassen und automatisch neu anfragen. Es hängt wirklich davon ab, welche Garantien Sie liefern müssen, aber eine Wiederholung ist möglicherweise eine bessere Wahl, wenn die Reihenfolge der Nachrichten kritisch ist. – bitwalker

Verwandte Themen