起動時に外部アプリケーションを起動してシャットダウンし、終了時に他のクリーンアップを行う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.stop
、linked 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
terminate
コールバックが呼び出される可能性を高めるには、サーバープロセスが出口をトラップする必要があります。ただし、それでも、状況によってはコールバックが呼び出されない場合があります(たとえば、プロセスが残酷に強制終了されたとき、またはプロセスがクラッシュしたときなど)。詳細は こちら をご覧ください。
前述のとおり、システムを丁寧にシャットダウンする場合は、:init.stop
を呼び出す必要があります。これにより、監視ツリーが再帰的にシャットダウンされ、terminate
コールバックが呼び出されます。
お気づきのように、突然のBEAM OSプロセスの終了を内部からキャッチする方法はありません。これは自己定義プロパティです。BEAMプロセスは突然終了するため、(終了したため)コードを実行できません????。したがって、BEAMが残酷に終了した場合、コールバックは呼び出されません。
BEAMの停止時に無条件に何かを実行したい場合は、別のOSプロセスからこれを検出する必要があります。あなたの正確なユースケースが何であるかはわかりませんが、これに対する強いニーズがあると仮定して、同じ(または別の)マシンで別のBEAMノードを実行すると、ここで動作する可能性があります。次に、あるノードの1つのプロセスが別のノードの別のプロセスを監視するようにして、BEAMが残忍に殺された場合でも対応できるようにします。
ただし、クリーンアップロジックを無条件に実行する必要がない場合、生活はより単純になります。そのため、terminate
のコードが必須であるか、どちらかといえばナイスオブナイトであるかどうかを検討してください。
2つの解決策を提案できます。
最初のものはドキュメントで言及されています。
プロセスは出口をトラップしないことに注意してください。
Genサーバープロセストラップを終了する必要があります。これをする:
Process.flag(:trap_exit, true)
これにより、プロセスがterminate/2
終了時。
しかし、別の解決策は、この初期化を上位の監督者に渡すことです。次に、スーパーバイザーに外部アプリケーション参照をgenサーバーに渡します。ただし、ここでは、必要に応じて外部アプリケーションを終了するためのterminate
のようなコールバックはありません。スーパーバイザが停止すると、外部アプリケーションは強制終了されます。
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)