【问题标题】:erlang OTP Supervisor crashingerlang OTP Supervisor崩溃
【发布时间】:2011-02-16 00:51:29
【问题描述】:

我正在阅读 Erlang 文档,试图了解设置 OTP gen_server 和主管的基础知识。每当我的 gen_server 崩溃时,我的主管也会崩溃。事实上,每当我在命令行上出现错误时,我的主管都会崩溃。

我希望 gen_server 在崩溃时重新启动。我希望命令行错误对我的服务器组件没有任何影响。我的主管根本不应该崩溃。

我正在使用的代码是一个基本的“回声服务器”,它会回复您发送的任何内容,以及一个最多每分钟重启回声服务器 5 次 (one_for_one) 的主管。我的代码:

echo_server.erl

-module(echo_server).
-behaviour(gen_server).

-export([start_link/0]).
-export([echo/1, crash/0]).
-export([init/1, handle_call/3, handle_cast/2]).

start_link() ->
    gen_server:start_link({local, echo_server}, echo_server, [], []).

%% public api
echo(Text) ->
    gen_server:call(echo_server, {echo, Text}).
crash() ->
    gen_server:call(echo_server, crash)..

%% behaviours
init(_Args) ->
    {ok, none}.
handle_call(crash, _From, State) ->
    X=1,
    {reply, X=2, State}.
handle_call({echo, Text}, _From, State) ->
    {reply, Text, State}.
handle_cast(_, State) ->
    {noreply, State}.

echo_sup.erl

-module(echo_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).

start_link() ->
    supervisor:start_link(echo_sup, []).
init(_Args) ->
    {ok,  {{one_for_one, 5, 60},
       [{echo_server, {echo_server, start_link, []},
             permanent, brutal_kill, worker, [echo_server]}]}}.

使用erlc *.erl 编译,这是一个示例运行:

Erlang R13B01 (erts-5.7.2) [source] [smp:2:2] [rq:2] [async-threads:0] [kernel-p
oll:false]

Eshell V5.7.2  (abort with ^G)
1> echo_sup:start_link().
{ok,<0.37.0>}
2> echo_server:echo("hi").
"hi"
3> echo_server:crash().   

=ERROR REPORT==== 5-May-2010::10:05:54 ===
** Generic server echo_server terminating 
** Last message in was crash
** When Server state == none
** Reason for termination == 
** {'function not exported',
       [{echo_server,terminate,
            [{{badmatch,2},
              [{echo_server,handle_call,3},
               {gen_server,handle_msg,5},
               {proc_lib,init_p_do_apply,3}]},
             none]},
        {gen_server,terminate,6},
        {proc_lib,init_p_do_apply,3}]}

=ERROR REPORT==== 5-May-2010::10:05:54 ===
** Generic server <0.37.0> terminating 
** Last message in was {'EXIT',<0.35.0>,
                           {{{undef,
                                 [{echo_server,terminate,
                                      [{{badmatch,2},
                                        [{echo_server,handle_call,3},
                                         {gen_server,handle_msg,5},
                                         {proc_lib,init_p_do_apply,3}]},
                                       none]},
                                  {gen_server,terminate,6},
                                  {proc_lib,init_p_do_apply,3}]},
                             {gen_server,call,[echo_server,crash]}},
                            [{gen_server,call,2},
                             {erl_eval,do_apply,5},
                             {shell,exprs,6},
                             {shell,eval_exprs,6},
                             {shell,eval_loop,3}]}}
** When Server state == {state,
                            {<0.37.0>,echo_sup},
                            one_for_one,
                            [{child,<0.41.0>,echo_server,
                                 {echo_server,start_link,[]},
                                 permanent,brutal_kill,worker,
                                 [echo_server]}],
                            {dict,0,16,16,8,80,48,
                                {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],
                                 []},
                                {{[],[],[],[],[],[],[],[],[],[],[],[],[],[],
                                  [],[]}}},
                            5,60,
                            [{1273,79154,701110}],
                            echo_sup,[]}
** Reason for termination == 
** {{{undef,[{echo_server,terminate,
                          [{{badmatch,2},
                            [{echo_server,handle_call,3},
                             {gen_server,handle_msg,5},
                             {proc_lib,init_p_do_apply,3}]},
                           none]},
             {gen_server,terminate,6},
             {proc_lib,init_p_do_apply,3}]},
     {gen_server,call,[echo_server,crash]}},
    [{gen_server,call,2},
     {erl_eval,do_apply,5},
     {shell,exprs,6},
     {shell,eval_exprs,6},
     {shell,eval_loop,3}]}
** exception exit: {{undef,
                        [{echo_server,terminate,
                             [{{badmatch,2},
                               [{echo_server,handle_call,3},
                                {gen_server,handle_msg,5},
                                {proc_lib,init_p_do_apply,3}]},
                              none]},
                         {gen_server,terminate,6},
                         {proc_lib,init_p_do_apply,3}]},
                    {gen_server,call,[echo_server,crash]}}
     in function  gen_server:call/2
4> echo_server:echo("hi").
** exception exit: {noproc,{gen_server,call,[echo_server,{echo,"hi"}]}}
     in function  gen_server:call/2
5>

【问题讨论】:

    标签: erlang erlang-otp erlang-supervisor gen-server


    【解决方案1】:

    从 shell 测试主管的问题是主管进程链接到 shell 进程。当 gen_server 进程崩溃时,退出信号会传播到崩溃并重新启动的 shell。

    为避免该问题,向主管添加如下内容:

    start_in_shell_for_testing() ->
        {ok, Pid} = supervisor:start_link(echo_sup, []),
        unlink(Pid).
    

    【讨论】:

    • 我要补充一点,这只是开发或调试时的有效方法。在实时生产系统中,最好尝试将代码包装在标准 OTP 应用程序中的正常监督树下。
    • 看这里stackoverflow.com/questions/6720472/…我已经有一段时间没有写erlang代码了,但是如果我没记错的话,发生的事情是,当gen_server崩溃时,退出信号被传播到所有链接的过程。您可以像上面的链接中那样捕获退出信号,但您几乎不想这样做。这是 erlang 让它崩溃的哲学。
    【解决方案2】:

    我建议您调试/跟踪您的应用程序以检查发生了什么。这对于理解 OTP 中的工作方式非常有帮助。

    在您的情况下,您可能需要执行以下操作。

    启动跟踪器:

    dbg:tracer().
    

    跟踪您的主管和您的 gen_server 的所有函数调用:

    dbg:p(all,c).
    dbg:tpl(echo_server, x).
    dbg:tpl(echo_sup, x).
    

    检查进程正在传递哪些消息:

    dbg:p(new, m).
    

    查看您的进程发生了什么(崩溃等):

    dbg:p(new, p).
    

    有关追踪的更多信息:

    http://www.erlang.org/doc/man/dbg.html

    http://aloiroberto.wordpress.com/2009/02/23/tracing-erlang-functions/

    希望这对现在和未来的情况有所帮助。

    提示: gen_server 行为期望回调 terminate/2 被定义和导出;)

    更新:terminate/2定义之后,崩溃的原因从跟踪中是显而易见的。看起来是这样的:

    我们 (75) 调用 crash/0 函数。这由 gen_server (78) 接收。

    (<0.75.0>) call echo_server:crash()
    (<0.75.0>) <0.78.0> ! {'$gen_call',{<0.75.0>,#Ref<0.0.0.358>},crash}
    (<0.78.0>) << {'$gen_call',{<0.75.0>,#Ref<0.0.0.358>},crash}
    (<0.78.0>) call echo_server:handle_call(crash,{<0.75.0>,#Ref<0.0.0.358>},none)
    

    呃,句柄调用的问题。我们有一个不匹配的...

    (<0.78.0>) exception_from {echo_server,handle_call,3} {error,{badmatch,2}}
    

    调用终止函数。服务器退出并取消注册。

    (<0.78.0>) call echo_server:terminate({{badmatch,2},
     [{echo_server,handle_call,3},
      {gen_server,handle_msg,5},
      {proc_lib,init_p_do_apply,3}]},none)
    (<0.78.0>) returned from echo_server:terminate/2 -> ok
    (<0.78.0>) exit {{badmatch,2},
     [{echo_server,handle_call,3},
      {gen_server,handle_msg,5},
      {proc_lib,init_p_do_apply,3}]}
    (<0.78.0>) unregister echo_server
    

    Supervisor (77) 接收到来自 gen_server 的退出信号并完成它的工作:

    (<0.77.0>) << {'EXIT',<0.78.0>,
                          {{badmatch,2},
                           [{echo_server,handle_call,3},
                            {gen_server,handle_msg,5},
                            {proc_lib,init_p_do_apply,3}]}}
    (<0.77.0>) getting_unlinked <0.78.0>
    (<0.75.0>) << {'DOWN',#Ref<0.0.0.358>,process,<0.78.0>,
                          {{badmatch,2},
                           [{echo_server,handle_call,3},
                            {gen_server,handle_msg,5},
                            {proc_lib,init_p_do_apply,3}]}}
    (<0.77.0>) call echo_server:start_link()
    

    好吧,它会尝试......因为它发生了 Filippo 所说的......

    【讨论】:

    • 感谢调试提示。 “函数未定义......终止”错误也让我感到困惑。 gen_server 行为不应该期望终止被定义,因为 echo_server 没有捕获出口。无论如何,这是根据文档;我还没读过OTP的代码。
    • 好吧,定义和导出 terminate/2 将删除 UNDEF,显示崩溃的真正原因(2 不匹配)。链接的东西是另一个故事......你现在让我有点困惑。 “gen_server 行为不应该期望终止,因为 echo_server 没有捕获出口”,你到底是什么意思?
    • 很棒的更新,谢谢。我指的文档是OTP Design Manual > gen_server > stopping"如果需要在终止前清理,则关闭策略必须是超时值,并且gen_server必须设置为在init函数中捕获退出信号。当命令关闭时,gen_server将调用回调函数terminate(shutdown, State)" 我的解释是根据 gen_server 的需要定义 terminate/2 是可选的。不是这样吗?
    【解决方案3】:

    另一方面,如果必须在控制台中测试重启策略,请使用控制台启动主管并检查 pman 以终止进程。

    您会看到 pman 使用相同的主管 Pid 刷新,但根据您在 restart-strategy 中设置的 MaxR 和 MaxT 使用不同的 worker Pid。

    【讨论】:

      猜你喜欢
      • 2011-08-17
      • 2017-09-08
      • 2015-05-12
      • 2011-05-19
      • 2013-11-01
      • 2013-11-23
      • 2021-12-19
      • 2020-01-31
      • 1970-01-01
      相关资源
      最近更新 更多