2016-09-28 3 views
7

Ich schreibe eine Elixir-App mit GenServer, die eine externe Anwendung beim Hochfahren startet und sie herunterfährt und andere Bereinigung beim Beenden durchführt. Ich habe Boot-Funktionalität in der init/1 Rückruf-und Bereinigungscode im terminate/2 Callback hinzugefügt.Graceful shutdown von GenServer

Der init Code funktioniert gut, wenn die GenServer gestartet wird, und die terminate Methode wird auch aufgerufen, wenn das :stop Signal manuell gesendet wird, aber in den Fällen von unerwarteten Abschaltungen und Unterbrechungen (wie im Fall von Drücken von Strg + C) In IEx wird der Beendigungscode nicht aufgerufen.


Derzeit habe ich über Tonnen von Themen im Forum, Blog-Posts und Dokumentation gegangen, darunter:

Von Elixir Docs - GenServers:

Wenn die GenServer ein Signal Ausgang empfängt (dh nicht :normal) von jedem Prozess, wenn er es nicht verläßt Trapping abrupt mit dem gleichen Grunde verlassen und so terminate/2 nicht nennen. Beachten Sie, dass ein Prozess NICHT Trap beendet standardmäßig und ein Exit-Signal gesendet wird, wenn ein verknüpfter Prozess beendet oder sein Knoten getrennt wird.

Daher ist nicht garantiert, dass terminate/2 aufgerufen wird, wenn ein GenServer beendet wird. Aus diesen Gründen empfehlen wir in der Regel wichtige Clean-up-Regeln in getrennten Prozessen entweder durch Verwendung von Überwachung oder durch Links selbst passieren.

aber ich habe absolut keine Ahnung, wie :init.stop, linked processes oder irgendetwas anderes zu bekommen mit diesem zu arbeiten (da dies mein erstes Mal mit GenServers ist).


Dies ist mein Code:

defmodule MyAwesomeApp do 
    use GenServer 

    def start do 
    GenServer.start_link(__MODULE__, nil) 
    end 

    def init(state) do 
    # Do Bootup stuff 

    IO.puts "Starting: #{inspect(state)}" 
    {:ok, state} 
    end 

    def terminate(reason, state) do 
    # Do Shutdown Stuff 

    IO.puts "Going Down: #{inspect(state)}" 
    :normal 
    end 
end 

MyAwesomeApp.start 

Antwort

4

Um die Wahrscheinlichkeit zu erhöhen, dass der Rückruf terminate aufgerufen wird, sollte der Serverprozess Exits abfangen. Aber selbst dann wird der Rückruf in einigen Situationen möglicherweise nicht aufgerufen (z. B. wenn der Prozess brutal beendet wird oder wenn er selbst abstürzt). Für weitere Details siehe here.

Wenn Sie Ihr System höflich herunterfahren möchten, sollten Sie :init.stop aufrufen, wodurch der Überwachungsbaum rekursiv beendet wird und terminate Callbacks aufgerufen werden.

Wie Sie festgestellt haben, gibt es keine Möglichkeit, abrupte BEAM OS-Prozess-Exits von innen zu erfassen. Es ist eine selbstdefinierende Eigenschaft: Der BEAM-Prozess wird plötzlich beendet, so dass kein Code mehr ausgeführt werden kann (da er beendet wurde). Daher wird, wenn BEAM brutal beendet wird, der Rückruf nicht aufgerufen.

Wenn Sie bedingungslos etwas tun möchten, wenn BEAM stirbt, müssen Sie dies von einem anderen Betriebssystemprozess erkennen. Ich bin nicht sicher, was genau Ihr Anwendungsfall ist, aber vorausgesetzt, Sie haben einige starke Bedürfnisse dafür, dann könnte hier ein anderer BEAM-Knoten auf demselben (oder einem anderen) Rechner laufen. Dann könnten Sie einen Prozess auf einem Knoten haben, der einen anderen Prozess auf einem anderen Knoten überwacht, so dass Sie auch dann reagieren können, wenn BEAM brutal getötet wird.

Allerdings wird Ihr Leben einfacher, wenn Sie nicht unbedingt einige Aufräumlogik ausführen müssen, also überlegen Sie, ob der Code in terminate ein Muss ist, oder eher ein nice-to-have.

3

Ich kann Ihnen zwei Lösungen vorschlagen.

Der erste wird in der Dokumentation erwähnt.

Beachten Sie, dass ein Prozess keine Exits abfängt.

Sie müssen Ihre Gen-Server-Prozess-Trap-Exits machen. Um dies zu tun:

Process.flag(:trap_exit, true) 

Das macht Ihren Prozess Aufruf terminate/2 beim Austritt.

Aber eine andere Lösung ist, diese Initialisierung an den oberen Supervisor zu übergeben. Lassen Sie dann den Supervisor den externen Anwendungsverweis an gen server übergeben. Aber hier haben Sie keinen terminate-ähnlichen Rückruf, um externe Anwendung gegebenenfalls zu beenden. Die externe Anwendung wird nur beendet, wenn der Supervisor anhält.

+0

'Process.flag (: trap_exit, true)' funktioniert nicht für mich. Kannst du mir bitte sagen, wo ich das in einem Genserver nennen soll? – Sheharyar

+0

Sie sollten es in den Prozess setzen, was bedeutet "init/1" – vfsoraki

+0

Versucht, dass das nicht funktioniert. – Sheharyar

0

Wenn Ihr zu bekommen versuchen, es in iex und Process.flag(:trap_exit, true) arbeiten funktioniert nicht, stellen Sie sicher, dass Sie statt GenServer.start_link sonst der Shell-Prozess abstürzt verwenden und das Trapping wird keine Rolle.

Hier ist ein Beispiel:

`` `

defmodule Server

use GenServer 
require Logger 

def start() do 
    GenServer.start(__MODULE__, [], []) 
end 


def init(_) do 
    Logger.info "starting" 
    Process.flag(:trap_exit, true) # your trap_exit call should be here 
    {:ok, :some_state} 
end 

# handle the trapped exit call 
def handle_info({:EXIT, _from, reason}, state) do 
    Logger.info "exiting" 
    cleanup(reason, state) 
    {:stop, reason, state} # see GenServer docs for other return types 
end 

# handle termination 
def terminate(reason, state) do 
    Logger.info "terminating" 
    cleanup(reason, state) 
    state 
end 

defp cleanup(_reason, _state) do 
    # Cleanup whatever you need cleaned up 
end 

Ende

` ``

In iex sehen Sie sollten jetzt ein eingeschlossenes Ausfahrt Anruf

iex> {:ok, pid} = GenServer.start() iex> Process.exit(pid, :something_bad)