我怎么知道这是由 erlang 中的主管重新启动我的进程的最后一个周期

Posted

技术标签:

【中文标题】我怎么知道这是由 erlang 中的主管重新启动我的进程的最后一个周期【英文标题】:How can I know when it's the last cycle of my process restarted by the supervisor in erlang 【发布时间】:2016-06-08 04:01:47 【问题描述】:

我有一个 simple_one_for_one 主管,其中有 gen_fsm 孩子。 我希望每个 gen_fsm 孩子只在最后一次终止时发送一条消息。 有没有办法知道最后一个周期是什么时候?

这是我的主管:

-module(data_sup).

-behaviour(supervisor).

%% API
-export([start_link/0,create_bot/3]).

%% Supervisor callbacks
-export([init/1]).

%%-compile(export_all).


%%%===================================================================
%%% API functions
%%%===================================================================

start_link() ->
  supervisor:start_link(local, ?MODULE, ?MODULE, []).

init([]) ->
 RestartStrategy = simple_one_for_one, 0, 1,
 ChildSpec = cs_fsm, cs_fsm, start_link, [],
 permanent, 2000, worker, [cs_fsm],
 Children = [ChildSpec],
 ok, RestartStrategy, Children.

create_bot(BotId, CNPJ,Pid) ->
  supervisor:start_child(?MODULE, [BotId, CNPJ, Pid]).

Pid 是启动监督者并下达命令启动子进程的进程的 Pid。

-module(cs_fsm).

-behaviour(gen_fsm).
-compile(export_all).

-define(SERVER, ?MODULE).
-define(TIMEOUT, 5000).

-record(params, botId, cnpj, executionId, pid).

%%%===================================================================
%%% API
%%%===================================================================

start_link(BotId, CNPJ, Pid) ->
  io:format("start_link...~n"),
  Params = #paramsbotId = BotId, cnpj = CNPJ, pid = Pid,
  gen_fsm:start_link(?MODULE, Params, []).


%%%===================================================================
%%% gen_fsm callbacks
%%%===================================================================

init(Params) ->
  io:format("initializing~n"),
  process_flag(trap_exit, true),
  ok, requesting_execution, Params, 0.

requesting_execution(timeout,Params) ->
  io:format("erqusting execution"),
  next_state, finished, Params,?TIMEOUT.

finished(timeout, Params) ->
  io:format("finished :)~n"),
  stop, normal, Params.

terminate(shutdown, _StateName, Params) ->
  Params#params.pid ! terminated, self(),Params,
  ok;

terminate(_Reason, _StateName, Params) ->
  ok.

我的观点是,如果进程在任何状态下失败,它应该仅在它最后一次被主管重新启动时发送消息(根据其重新启动策略)。

如果gen_fsm 失败,它是否从具有相同状态数据的相同状态重新启动?如果不是,我怎么能导致它发生?

【问题讨论】:

好的,现在经过您的进一步解释,我明白这不是关于 gen_fsm 由于某些预期的触发器而终止,而是因为进程死亡而异常终止?您只想计算主管何时重新启动 cs_fsm 并在主管不再重新启动它时发送消息,以防它再次死亡? 是的。有没有办法做到这一点? 为了让我的问题更笼统:主管可以在上次重新启动它的孩子时为其孩子做出不同的终止吗? 主管如何知道它最后一次重启了孩子?在孩子真正崩溃之前,主管无法计算出这是最后一次崩溃,因为它甚至不知道孩子是否或何时会崩溃。只有在崩溃监督员之后才能扣除是否允许重新启动孩子。在编辑中查看我更完整的答案。顺便说一句,您为什么想知道孩子最后一次重新启动?如果主管无法重新启动孩子,因为它崩溃了太多次,这是一个不可恢复的情况,主管应该自己死! 我希望孩子通知崩溃到某个进程的原因,但仅在最后一次崩溃时通知。也许我应该在状态记录中有一个计数(所以我防止使用 ets 表),我会在终止时增加它,然后我可以知道它运行了多少次。顺便说一句,当我的 gen_fsm 孩子崩溃时,它会在之前的状态下重新启动吗? 【参考方案1】:

您可以添加将消息发送到Module:terminate/3 函数,当StateName 函数之一返回stop,Reason,NewStateData 以指示应该停止gen_fsm 时调用该函数。

gen_fsm 是一个有限状态机,因此您可以决定它如何在状态之间转换。触发最后一个循环的某些东西也可能在传递给Module:StateName/3StateData 中设置一些东西,以便处理状态的函数知道它是最后一个循环。除非您提供一些我们可以分析和评论的代码,否则很难给出更具体的答案。

进一步澄清后编辑:

Supervisor 不会通知它的孩子它是什么时候重新启动它们的,它也不能通知孩子它是最后一次重新启动。这只是因为它不知道它将是最后一次,直到主管进程实际上再次崩溃,主管无法预测。只有在孩子崩溃后,主管才能计算出孩子在一段时间内崩溃了多少次,是否允许再次重新启动孩子,或者这是最后一次重启,现在主管也该死了。

但是,没有什么可以阻止孩子注册,例如在 ETS 表中,它被重新启动了多少次。但它当然无助于扣除最后一次重启。

编辑 2:

当主管重新启动子进程时,它会使用标准 init 函数从头开始。孩子在崩溃之前的任何先前状态都将丢失。

请注意,崩溃是一种特殊情况,并不总是可以恢复状态,因为崩溃可能会破坏状态。与其尝试恢复状态或询问主管何时重新启动孩子,为什么不首先防止崩溃发生呢?你有两个选择:

I. 使用try/catch 捕捉任何异常情况并采取相应措施。有可能捕获任何会导致进程崩溃并导致主管重新启动它的错误。您可以将try/catch 添加到gen_fsm 进程内的任何入口函数,以便在服务器崩溃之前捕获任何错误情况。见example function 1或example function 2:

read() ->
    try
        try_home() orelse try_path(?MAIN_CFG) orelse
            begin io:format("Some Error", []) end
    catch
        throw:Term -> error, Term
    end.

try_read(Path) ->
    try
        file:consult(Path)
    catch
        error:Error -> error, Error
    end.

II. 生成一个新进程来处理作业并在进程终止时捕获EXIT 信号。这允许gen_fsm 异步处理作业并以自定义方式处理任何错误(不一定像主管那样重新启动进程)。这部分标题为Error Handling 解释了如何捕获来自子进程的exit 信号。这是gen_server 中的example of trapping signals。检查包含几个子句的handle_info 函数,以捕获来自子进程的不同类型的EXIT 消息。

init([Cfg, Id, Mode]) ->
    process_flag(trap_exit, true),
    (...)


handle_info('EXIT', _Pid, normal, State) ->
    noreply, State;
handle_info('EXIT', _Pid, noproc, State) ->
    noreply, State;
handle_info('EXIT', Pid, Reason, State) ->
    log_exit(Pid, Reason),
    check_done(error, Pid, State);
handle_info(_, State) ->
    noreply, State.

【讨论】:

谢谢!!我实际上在子进程中使用 ets 表。关于处理错误我更喜欢“让它崩溃”的方法。 “让它崩溃的哲学”并不意味着你不应该尽可能地捕捉错误。这仅意味着您不应该进行防御性编码。使用 try-catch 比尝试添加更多逻辑来处理故障要好。请参阅这张幻灯片:qconlondon.com/london-2011/qconlondon.com/dl/qcon-london-2011/… 这是一般性评论,因为我不知道您的架构的确切设计:)

以上是关于我怎么知道这是由 erlang 中的主管重新启动我的进程的最后一个周期的主要内容,如果未能解决你的问题,请参考以下文章

启动远程 Erlang 节点

Erlang 主管有一个关键的孩子

Erlang OTP 主管 gen_tcp - error,eaddrinuse

在erlang中注册全局主管和本地主管有啥区别

退出的主管

如何分发 Erlang 进程(主管行为)?