Erlang OTP Supervisor 崩溃

10

我正在学习Erlang文档,尝试理解设置OTP gen_server和supervisor的基础知识。每当我的gen_server崩溃时,我的supervisor也会崩溃。实际上,每当我在命令行上出现错误时,我的supervisor都会崩溃。

我期望gen_server在崩溃时被重启。我期望命令行错误对我的服务器组件完全没有影响。我的supervisor根本不应该崩溃。

我正在使用一个基本的“回声服务器”代码,它将回复任何你发送的内容,并且一个supervisor每分钟最多重新启动echo_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>
3个回答

18

从 Shell 测试主管的问题在于主管进程与 Shell 进程相链接。当 gen_server 进程崩溃时,退出信号会传播到 Shell 进程,导致其崩溃并重新启动。

为避免这个问题,请向主管添加类似以下内容:

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

5
我会增加一点内容,这仅适用于开发或调试过程中。在生产系统中,最好尝试将您的代码包装在标准OTP应用程序下的普通监控树中。 - I GIVE TERRIBLE ADVICE
看一下这里https://dev59.com/zGw15IYBdhLWcg3wSJk3。我已经有一段时间没有编写Erlang代码了,但如果我记得好的话,现在发生的是当gen_server崩溃时,退出信号会传播到所有链接的进程中。你可以像上面的链接中那样捕获退出信号,但你几乎永远不想这样做。这是Erlang让它崩溃哲学。 - filippo

10
我建议您调试/跟踪应用程序以查看发生了什么。这对于理解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)

嗯,在调用处理程序时出现问题。我们遇到了一个匹配错误(badmatch)...

(<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说的事情之后...


谢谢你的调试提示。"function not defined ... terminate" 错误也让我感到困惑。gen_server行为不应该期望定义terminate,因为echo_server没有捕获退出信号。至少根据文档是这样说的;我还没有阅读OTP的代码。 - drfloob
好的,定义和导出terminate /2将删除UNDEF,显示崩溃的真正原因(在2上进行了错误匹配)。而链接方面是另一回事...您现在有点让我困惑了。您所说的“gen_server行为不应该期望定义terminate,因为echo_server没有捕获退出”具体指的是什么? - Roberto Aloi
太棒了,感谢您的更新。我所参考的文档是OTP设计手册>gen_server>停止"如果在终止之前需要进行清理,则关闭策略必须是超时值,并且在init函数中必须将gen_server设置为陷阱退出信号。当命令关闭时,gen_server将调用回调函数terminate(shutdown, State)"我的理解是定义terminate/2是可选的,根据您的gen_server的需求而定。这不是这种情况吗? - drfloob

2

另一方面,如果必须在控制台中测试重新启动策略,请使用控制台启动监督器,并使用pman检查以杀死进程。

您会发现,根据您在重新启动策略中设置的MaxR和MaxT,pman将以相同的监督器Pid但不同的工作进程Pids进行刷新。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接