сбой в erlang OTP Supervisor

Я работаю с документацией Erlang, пытаясь понять основы настройки OTP gen_server и супервизора. Каждый раз, когда мой gen_server выходит из строя, мой супервизор тоже вылетает. Фактически, всякий раз, когда у меня появляется ошибка в командной строке, мой супервизор вылетает.

Я ожидаю, что gen_server будет перезапущен при сбое. Я ожидаю, что ошибки командной строки не имеют никакого отношения к моим серверным компонентам. Мой руководитель вообще не должен падать.

Код, с которым я работаю, - это базовый «эхо-сервер», который отвечает всем, что вы отправляете, и супервизор, который перезапускает 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>

person drfloob    schedule 05.05.2010    source источник


Ответы (3)


Проблема с супервизорами тестирования из оболочки заключается в том, что процесс супервизора связан с процессом оболочки. Когда процесс gen_server аварийно завершает работу, сигнал выхода распространяется до оболочки, которая дает сбой и перезапускается.

Чтобы избежать проблемы, добавьте в супервизор что-то вроде этого:

start_in_shell_for_testing() ->
    {ok, Pid} = supervisor:start_link(echo_sup, []),
    unlink(Pid).
person filippo    schedule 05.05.2010
comment
Я бы добавил, что это допустимый подход только при разработке или отладке. В действующей производственной системе лучше попробовать обернуть свой код в стандартное приложение OTP под обычным деревом надзора. - person I GIVE TERRIBLE ADVICE; 08.05.2010
comment
Взгляните сюда stackoverflow.com/questions/ 6720472 / Я не писал код на erlang в течение некоторого времени, но если я хорошо помню, что происходит, когда gen_server аварийно завершает работу, сигнал выхода распространяется на все связанные процессы. Вы можете перехватить сигнал выхода, как в ссылке выше, но вы почти никогда не захотите этого делать. Это эрланг, пусть он разрушит философию. - person filippo; 05.03.2014

Я бы посоветовал вам отладить / отследить ваше приложение, чтобы проверить, что происходит. Это очень помогает понять, как все работает в 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

Супервизор (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()

Ну, это пытается ... Раз уж случилось то, что сказал Филиппо ...

person Roberto Aloi    schedule 05.05.2010
comment
спасибо за советы по отладке. Функция не определена ... ошибка terminate меня тоже смутила. Поведение gen_server не должно ожидать определения terminate, поскольку echo_server не перехватывает выходы. Во всяком случае, это согласно документации; Я еще не читал код OTP. - person drfloob; 05.05.2010
comment
Что ж, определение и экспорт terminate / 2 удалит UNDEF, показывая реальную причину сбоя (плохое совпадение на 2). Связывание - это отдельная история ... Вы меня сейчас немного запутали. Что вы имеете в виду, говоря именно о поведении gen_server, не следует ожидать определения terminate, поскольку echo_server не захватывает выходы? - person Roberto Aloi; 06.05.2010
comment
Отличное обновление, спасибо за это. Я имею в виду документы Руководство по проектированию OTP ›gen_server› stop . Если необходимо очистить перед завершением, стратегия выключения должна быть значением тайм-аута, а gen_server должен быть настроен на перехват сигналов выхода в функции инициализации. При получении команды на выключение gen_server затем вызовет функцию обратного вызова terminate (shutdown, State) Моя интерпретация такова, что определение terminate / 2 является необязательным, исходя из потребностей вашего gen_server. Разве это не так? - person drfloob; 07.05.2010

С другой стороны, если вообще необходимо протестировать стратегию перезапуска из консоли, используйте консоль для запуска супервизора и проверьте с помощью pman, чтобы убить процесс.

Вы увидите, что pman обновляется с одним и тем же Pid супервизора, но с разными Pid рабочего процесса в зависимости от MaxR и MaxT, которые вы установили в стратегии перезапуска.

person Muthukumaran    schedule 17.03.2012