2016-09-28 16 views
7

Piszę aplikację Elixir z GenServer, która uruchamia zewnętrzną aplikację podczas startu i zamyka ją, a następnie wykonuje inne prace porządkowe przy wyjściu. Dodałem funkcję uruchamiania w kodzie oddzwonienia i czyszczenia init/1 w wywołaniu zwrotnym .Wdzięczne wyłączenie GenServer

Kod init działa dobrze, gdy GenServer jest uruchamiany, a metoda terminate nazywany jest również, gdy sygnał :stop jest ręcznie wysłany, ale w przypadkach nieprzewidzianych przestojów i przerywa (jak w przypadku uderzenia Ctrl + C) w IEx, kod zakończenia nie jest wywoływany.


Obecnie Poszedłem nad tonami wątków forum, blogach i dokumentacji, w tym:

Od Elixir Docs - GenServers:

Jeśli GenServer odbiera sygnał wyjściowy (to nie jest :normal) od jakiegokolwiek procesu, gdy nie jest schwytanie wyjścia będzie wyjść nagle z tego samego powodu i tak nie nazywają terminate/2. Zauważ, że proces kończy domyślnie pułapkę NOT, a sygnał wyjściowy jest wysyłany , gdy połączony proces zakończy się lub jego węzeł zostanie rozłączony.

Dlatego nie można zagwarantować, że terminate/2 zostanie wywołany, gdy zakończy się GenServer. Z tego powodu zazwyczaj zalecamy ważne zasady czyszczenia, które mogą się zdarzyć w oddzielnych procesach, albo za pomocą monitoringu lub przez same łącza.

ale nie mam bladego pojęcia, jak dostać :init.stop, linked processes lub cokolwiek innego do pracy z tym (ponieważ jest to mój pierwszy raz z GenServers).


To jest mój kod:

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 

Odpowiedz

4

Aby zwiększyć prawdopodobieństwo wywołania zwrotnego o wartości terminate, proces serwera powinien wychwytywać wyjścia. Jednak nawet w tym przypadku wywołanie zwrotne może nie zostać wywołane w niektórych sytuacjach (np. Gdy proces zostanie brutalnie zabity lub sam się zawiesi). Aby uzyskać więcej informacji, zobacz here.

Jak już wspomniano, jeśli chcesz grzecznie zamknąć system, powinieneś wywołać :init.stop, które spowoduje rekursywne zamknięcie drzewa nadzoru, powodując wywołania zwrotne w trybie terminate.

Jak zauważyłeś, nie ma sposobu na złapanie nagłego wyjścia procesu BEAM OS od wewnątrz. Jest to właściwość samokreślająca: proces BEAM kończy się nagle, więc nie może uruchomić żadnego kodu (od zakończenia). Jeśli więc BEAM zostanie brutalnie przerwane, wywołanie zwrotne nie zostanie wywołane.

Jeśli bezwarunkowo chcesz coś zrobić po śmierci BEAM, musisz to wykryć z innego procesu systemu operacyjnego. Nie jestem pewien, jaki jest twój dokładny przypadek użycia, ale zakładając, że masz do tego jakieś silne potrzeby, wtedy może działać tutaj kolejny węzeł BEAM, na tym samym (lub innym) komputerze. Wtedy możesz mieć jeden proces na jednym węźle monitorujący inny proces na innym węźle, więc możesz zareagować, nawet jeśli BEAM zostanie brutalnie zabity.

Jednak twoje życie będzie prostsze, jeśli nie będziesz musiał bezwarunkowo uruchamiać logiki czyszczenia, więc zastanów się, czy kod w terminate jest konieczny, czy raczej niezły.

3

mogę zaproponować Ci dwa rozwiązania.

Pierwsza z nich jest wymieniona w dokumentach.

Należy pamiętać, że proces NIE przechwytuje wyjść.

Musisz wykonać procedurę wyjścia z pułapki procesowej swojego serwera genów. Aby to zrobić:

Process.flag(:trap_exit, true) 

To sprawia, że ​​połączenie procesu terminate/2 momencie wyjścia.

Ale innym rozwiązaniem jest przekazanie tej inicjalizacji do górnego opiekuna. Następnie przekaż nadzorcy zewnętrzne referencje aplikacji do serwera genów. Ale tutaj nie masz wywołania zwrotnego podobnego do terminate, aby wyjść z zewnętrznej aplikacji, jeśli to konieczne. Aplikacja zewnętrzna zostanie po prostu zabita, gdy superwizor zostanie zatrzymany.

+0

'Process.flag (: trap_exit, true)' nie działa dla mnie. Czy możesz powiedzieć, gdzie powinienem to nazwać w genserverze? – Sheharyar

+0

Powinieneś ustawić to w procesie, co znaczy "init/1" – vfsoraki

+0

Próbowałem, że nie działa. – Sheharyar

0

Jeśli próbujesz zmusić go do pracy w iex i Process.flag(:trap_exit, true) nie działa, upewnij się, że używasz zamiast GenServer.start_link przeciwnym razie proces powłoki padnie i wychwytujący nie będzie miało znaczenia.

Oto przykład:

`` `

defmodule Server nie

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 

koniec

` ``

W IEX powinieneś teraz zobaczyć uwięzionego zjazd zadzwoń pod numer

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