web-dev-qa-db-ja.com

GenServerの正常なシャットダウン

起動時に外部アプリケーションを起動してシャットダウンし、終了時に他のクリーンアップを行うGenServerでElixirアプリを作成しています。 init/1 コールバックに起動機能を追加し、 terminate/2 コールバックにコードをクリーンアップしました。

initコードはGenServerの起動時に正常に機能し、terminateメソッドは:stop信号が手動で送信されたときにも呼び出されますが、 IExで予期しないシャットダウンや割り込みが発生した場合(Ctrl + Cを押した場合など)、終了コードは呼び出されません


現在、私はたくさんのフォーラムスレッド、ブログ投稿、ドキュメントを調べてきました。

From Elixir Docs-GenServers

GenServerが出口をトラップしていないときに、プロセスから出口シグナル(:normalではない)を受け取った場合、同じ理由で突然終了するため、terminate/2を呼び出さないでください。プロセスはデフォルトで終了する[〜#〜] [〜#〜](〜#〜]トラップを実行し、リンクされたプロセスが終了するかそのノードが切断されると終了信号が送信されることに注意してください。

したがって、GenServerが存在するときにterminate/2が呼び出されるとは限りません。このような理由により、通常は、監視を使用するか、リンク自体を使用して、別個のプロセスで重要なクリーンアップルールを実行することをお勧めします。

しかし、これを使用して:init.stoplinked processesなどを取得する方法はまったくわかりません(これはGenServerを初めて使用するためです)。


これは私のコードです:

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
19
Sheharyar

terminateコールバックが呼び出される可能性を高めるには、サーバープロセスが出口をトラップする必要があります。ただし、それでも、状況によってはコールバックが呼び出されない場合があります(たとえば、プロセスが残酷に強制終了されたとき、またはプロセスがクラッシュしたときなど)。詳細は こちら をご覧ください。

前述のとおり、システムを丁寧にシャットダウンする場合は、:init.stopを呼び出す必要があります。これにより、監視ツリーが再帰的にシャットダウンされ、terminateコールバックが呼び出されます。

お気づきのように、突然のBEAM OSプロセスの終了を内部からキャッチする方法はありません。これは自己定義プロパティです。BEAMプロセスは突然終了するため、(終了したため)コードを実行できません????。したがって、BEAMが残酷に終了した場合、コールバックは呼び出されません。

BEAMの停止時に無条件に何かを実行したい場合は、別のOSプロセスからこれを検出する必要があります。あなたの正確なユースケースが何であるかはわかりませんが、これに対する強いニーズがあると仮定して、同じ(または別の)マシンで別のBEAMノードを実行すると、ここで動作する可能性があります。次に、あるノードの1つのプロセスが別のノードの別のプロセスを監視するようにして、BEAMが残忍に殺された場合でも対応できるようにします。

ただし、クリーンアップロジックを無条件に実行する必要がない場合、生活はより単純になります。そのため、terminateのコードが必須であるか、どちらかといえばナイスオブナイトであるかどうかを検討してください。

10
sasajuric

2つの解決策を提案できます。

最初のものはドキュメントで言及されています。

プロセスは出口をトラップしないことに注意してください。

Genサーバープロセストラップを終了する必要があります。これをする:

Process.flag(:trap_exit, true)

これにより、プロセスがterminate/2終了時。

しかし、別の解決策は、この初期化を上位の監督者に渡すことです。次に、スーパーバイザーに外部アプリケーション参照をgenサーバーに渡します。ただし、ここでは、必要に応じて外部アプリケーションを終了するためのterminateのようなコールバックはありません。スーパーバイザが停止すると、外部アプリケーションは強制終了されます。

5
vfsoraki

iexおよびProcess.flag(:trap_exit, true)で機能させるための試みが機能しない場合は、GenServer.startではなくGenServer.start_linkを使用していることを確認してください。そうしないと、シェルプロセスがクラッシュしますトラッピングは関係ありません。

次に例を示します。

defmodule Server do

    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

end

Iexでは、トラップされた出口呼び出しが表示されるはずです。

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