GenServer 的正常关闭
Graceful shutdown of GenServer
我用 GenServer 编写了一个 Elixir 应用程序,它在启动时启动外部应用程序并关闭它并在退出时进行其他清理。我在 init/1
callback and cleanup code in the terminate/2
回调中添加了启动功能。
GenServer启动时init
代码运行正常,手动发送:stop
信号时也会调用terminate
方法,但在在 IEx 中意外关闭和中断的情况(如按 Ctrl+C 的情况),不会调用终止代码。
目前,我浏览了大量的论坛帖子、博客文章和文档,包括:
- Getting Started: GenServers
- Elixir-Lang-Talk: Graceful shutdown of GenServer(s) on exiting iex -S mix
- Elixir-Lang-Talk: Stopping Genserver vs Process.exit
From Elixir Docs - GenServers:
If the GenServer
receives an exit signal (that is not :normal
)
from any process when it is not trapping exits it will exit abruptly
with the same reason and so not call terminate/2
. Note that a
process does NOT trap exits by default and an exit signal is sent
when a linked process exits or its node is disconnected.
Therefore it is not guaranteed that terminate/2
is called when a
GenServer
exits. For such reasons, we usually recommend important
clean-up rules to happen in separated processes either by use of
monitoring or by links themselves.
但我完全不知道如何获得 :init.stop
、linked processes
或其他任何东西(因为这是我第一次使用 GenServers)。
这是我的代码:
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
我可以向您推荐两种解决方案。
文档中提到了第一个。
Note that a process does NOT trap exits.
您必须让您的生成服务器进程陷阱退出。为此:
Process.flag(:trap_exit, true)
这会使您的进程在退出时调用 terminate/2
。
但是还有一个解决办法,就是把这个初始化交给上层主管。然后让主管将外部应用程序引用传递给 gen 服务器。但是在这里,您没有类似 terminate
的回调来在必要时退出外部应用程序。当主管停止时,外部应用程序将被杀死。
为了增加调用 terminate
回调的机会,服务器进程应该捕获退出。然而,即便如此,在某些情况下也可能不会调用回调(例如,当进程被残忍地杀死时,或者当它自身崩溃时)。有关详细信息,请参阅 here。
如前所述,如果你想礼貌地关闭你的系统,你应该调用 :init.stop
,这将递归关闭监督树,导致 terminate
回调被调用。
如您所见,无法捕捉到 BEAM OS 进程从内部突然退出。这是一个自定义的 属性:BEAM 进程突然终止,所以它不能 运行 任何代码(因为它终止了)。因此,如果 BEAM 被粗暴地终止,则不会调用回调。
如果您无条件地想在 BEAM 死机时做某事,您需要从另一个 OS 进程中检测到这一点。我不确定您的确切用例是什么,但假设您对此有强烈需求,那么 运行 在同一台(或另一台)机器上的另一个 BEAM 节点可以在这里工作。然后你可以让一个节点上的一个进程监视另一个节点上的另一个进程,这样即使 BEAM 被残忍地杀死你也可以做出反应。
然而,如果你不需要无条件地 运行 一些清理逻辑,你的生活会更简单,所以考虑 terminate
中的代码是必须的,还是更好的-有。
如果您尝试让它在 iex
和 Process.flag(:trap_exit, true)
中工作但不起作用,请确保您使用的是 GenServer.start
而不是 GenServer.start_link
,否则 shell 进程将崩溃,陷阱无关紧要。
这是一个例子:
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)
我用 GenServer 编写了一个 Elixir 应用程序,它在启动时启动外部应用程序并关闭它并在退出时进行其他清理。我在 init/1
callback and cleanup code in the terminate/2
回调中添加了启动功能。
GenServer启动时init
代码运行正常,手动发送:stop
信号时也会调用terminate
方法,但在在 IEx 中意外关闭和中断的情况(如按 Ctrl+C 的情况),不会调用终止代码。
目前,我浏览了大量的论坛帖子、博客文章和文档,包括:
- Getting Started: GenServers
- Elixir-Lang-Talk: Graceful shutdown of GenServer(s) on exiting iex -S mix
- Elixir-Lang-Talk: Stopping Genserver vs Process.exit
From Elixir Docs - GenServers:
If the
GenServer
receives an exit signal (that is not:normal
) from any process when it is not trapping exits it will exit abruptly with the same reason and so not callterminate/2
. Note that a process does NOT trap exits by default and an exit signal is sent when a linked process exits or its node is disconnected.Therefore it is not guaranteed that
terminate/2
is called when aGenServer
exits. For such reasons, we usually recommend important clean-up rules to happen in separated processes either by use of monitoring or by links themselves.
但我完全不知道如何获得 :init.stop
、linked processes
或其他任何东西(因为这是我第一次使用 GenServers)。
这是我的代码:
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
我可以向您推荐两种解决方案。
文档中提到了第一个。
Note that a process does NOT trap exits.
您必须让您的生成服务器进程陷阱退出。为此:
Process.flag(:trap_exit, true)
这会使您的进程在退出时调用 terminate/2
。
但是还有一个解决办法,就是把这个初始化交给上层主管。然后让主管将外部应用程序引用传递给 gen 服务器。但是在这里,您没有类似 terminate
的回调来在必要时退出外部应用程序。当主管停止时,外部应用程序将被杀死。
为了增加调用 terminate
回调的机会,服务器进程应该捕获退出。然而,即便如此,在某些情况下也可能不会调用回调(例如,当进程被残忍地杀死时,或者当它自身崩溃时)。有关详细信息,请参阅 here。
如前所述,如果你想礼貌地关闭你的系统,你应该调用 :init.stop
,这将递归关闭监督树,导致 terminate
回调被调用。
如您所见,无法捕捉到 BEAM OS 进程从内部突然退出。这是一个自定义的 属性:BEAM 进程突然终止,所以它不能 运行 任何代码(因为它终止了)。因此,如果 BEAM 被粗暴地终止,则不会调用回调。
如果您无条件地想在 BEAM 死机时做某事,您需要从另一个 OS 进程中检测到这一点。我不确定您的确切用例是什么,但假设您对此有强烈需求,那么 运行 在同一台(或另一台)机器上的另一个 BEAM 节点可以在这里工作。然后你可以让一个节点上的一个进程监视另一个节点上的另一个进程,这样即使 BEAM 被残忍地杀死你也可以做出反应。
然而,如果你不需要无条件地 运行 一些清理逻辑,你的生活会更简单,所以考虑 terminate
中的代码是必须的,还是更好的-有。
如果您尝试让它在 iex
和 Process.flag(:trap_exit, true)
中工作但不起作用,请确保您使用的是 GenServer.start
而不是 GenServer.start_link
,否则 shell 进程将崩溃,陷阱无关紧要。
这是一个例子:
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)