Почему мой супервизор терпит неудачу на start_child с помощью undef?

Я пытаюсь запустить simple_one_for_one supervisor где supervisor а также worker размещены в отдельных модулях, и я продолжаю получать следующую ошибку при использовании supervisor:start_child:

>A=sup:start_link().
>B=supervisor:start_child(A,[]).
{error,{'EXIT',{undef,[{worker,start_link,[],[]},
                       {supervisor,do_start_child_i,3,
                                   [{file,"supervisor.erl"},{line,379}]},
                       {supervisor,handle_call,3,
                                   [{file,"supervisor.erl"},{line,404}]},
                       {gen_server,try_handle_call,4,
                                   [{file,"gen_server.erl"},{line,661}]},
                       {gen_server,handle_msg,6,
                                   [{file,"gen_server.erl"},{line,690}]},
                       {proc_lib,init_p_do_apply,3,
                                 [{file,"proc_lib.erl"},{line,249}]}]}}}

Руководитель

-module(sup).
-behaviour(supervisor).
-compile([export_all]).

start_link()->
    {ok,Pid}=supervisor:start_link(?MODULE,[]),
    io:format("sugi pl"),
    Pid.



init(_Args) ->
     RestartStrategy = {simple_one_for_one, 10, 60},
     ChildSpec = {
                  worker, 
                  {worker, start_link, []},  //tried adding here a parameter in the A
                  permanent,
                  brutal_kill, 
                  worker,
                  [sup]
                },
    {ok, {RestartStrategy,[ChildSpec]}}.

Рабочий

-module(worker).
-compile(export_all).


start_link([Arg])->  //tried both [Arg] and Arg
    {ok,Pid}=spawn_link(?MODULE,init,[]),
    Pid.


init([Time])->
    receive->
        {From,Msg}->From !{Time,Msg},
                     init(Time)
    end.

Команда

>c("[somepath]/sup.erl"),A=sup:start_link(),B=supervisor:start_child(A,[]).

Я ясно вижу, что проблема заключается в попытке добавить ребенка. initфункция не вызывается должным образом, но я не понимаю, почему.(Плохое совпадение)
Я попытался добавить параметр вA из MFA из ChildSpec но безрезультатно

2 ответа

Решение

С вашим кодом есть ряд проблем.

  • Возвращаемое значение sup:start_link/0неправильно; вы возвращаете Pid вместо{ok, Pid}.
  • Хотя это не совсем неправильно, вы используете supervisor:start_link/2, который не регистрирует имя супервизора. Имя удобно, поэтому лучше использоватьsupervisor:start_link/3:

    supervisor:start_link({local, ?MODULE}, ?MODULE, []),
    

    Это связывает имя модуля с его идентификатором процесса, что позволяет вам использовать имя процесса в командах оболочки, а не использовать переменные pid.

  • У вас есть io:format/2 вызывать sup:start_link/0, предположительно для отладки. Гораздо лучший способ отладки - вызватьsys:trace(sup, true) из вашей оболочки, как только вы запустили supруководитель. Вы также можете отключить его из оболочки, указавfalse вместо того true как второй аргумент.

Устранение вышеуказанных проблем позволяет использовать следующее определение sup:start_link/0:

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

Перекомпилируем, запустим супервизор, затем скомпилируем worker (после исправления синтаксических ошибок), а затем отслеживаем супервизор, когда мы пытаемся запустить дочерний элемент:

1> c(sup).
sup.erl:3: Warning: export_all flag enabled - all functions will be exported
{ok,sup}
2> sup:start_link().
{ok,<0.94.0>}
3> sys:trace(sup, true).
ok
4> c(worker).
worker.erl:2: Warning: export_all flag enabled - all functions will be exported
worker.erl:5: Warning: variable 'Arg' is unused
{ok,worker}
5> supervisor:start_child(sup, []).
*DBG* sup got call {start_child,[]} from <0.81.0>
*DBG* sup sent {error,
                   {'EXIT',
                       {undef,
                           [{worker,start_link,[],[]},
                            {supervisor,do_start_child_i,3,
                                [{file,"supervisor.erl"},{line,379}]},
...

Этот сокращенный вывод трассировки показывает, что sup умер, когда он попытался начать worker позвонив worker:start_link/0[]указывает на отсутствие аргументов). Дочерняя спецификация сообщаетsup чтобы начать это так, поскольку он содержит

{worker, start_link, []}

и мы начали ребенка через supervisor:start_child(sup, []). Дляsimple_one_for_one child, аргументы, отправленные его функции запуска, состоят из списка аргументов из дочерней спецификации в сочетании с аргументами, указанными в вызове к supervisor:start_child/2; в данном случае это эквивалент[] ++ [] который совпадает с [], что указывает на отсутствие аргументов. Давайте изменимworker:start_link/1 функция для worker:start_link/0 вместо этого перекомпилируйте его и попробуйте еще раз:

6> c(worker).
worker.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,worker}
7> supervisor:start_child(sup, []).
*DBG* sup got call {start_child,[]} from <0.81.0>
*DBG* sup sent {error,
                   {'EXIT',
                       {{badmatch,<0.94.0>},
                        [{worker,start_link,0,[{file,"worker.erl"},{line,6}]},
...

На этот раз сокращенный вывод показывает badmatch. Это потому чтоspawn_link/3 возвращает pid, но worker:start_link/0 ожидает его возвращения {ok, Pid}. Давайте исправим это, а также исправим возвращаемое значение, чтобы оно было{ok, Pid} вместо просто Pid:

start_link()->
    Pid = spawn_link(?MODULE,init,[]),
    {ok, Pid}.

Затем перекомпилируем и попробуем еще раз:

8> c(worker).
worker.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,worker}
9> supervisor:start_child(sup, []).
*DBG* sup got call {start_child,[]} from <0.81.0>
*DBG* sup sent {ok,<0.106.0>} to <0.81.0>, new state {state,
                                                      {local,sup},
                                                      simple_one_for_one,
                                                      {[worker],
                                                       #{worker =>
                                                          {child,undefined,
                                                           worker,
                                                           {worker,
                                                            start_link,[]},
                                                           permanent,
                                                           brutal_kill,worker,
                                                           [sup]}}},
                                                      {maps,
                                                       #{<0.106.0> => []}},
                                                      10,60,[],0,sup,[]}
*DBG* sup got {'EXIT',<0.106.0>,{undef,[{worker,init,[],[]}]}}

Хорошо, на этот раз супервизор действительно запустил ребенка, но он сразу умер, потому что пытался вызвать worker:init/0 но только worker:init/1определено. Поскольку ребенок умирает немедленно, супервизор неоднократно пытается запустить его в соответствии со своей стратегией перезапуска:

RestartStrategy = {simple_one_for_one, 10, 60},

Поскольку это серьезная ошибка, она выходит из строя каждый раз мгновенно, и супервизор умирает после 10 неудачных перезапусков за 60 секунд или меньше, как и должно:

=SUPERVISOR REPORT==== 20-Apr-2020::10:43:43.557307 ===
    supervisor: {local,sup}
    errorContext: shutdown
    reason: reached_max_restart_intensity
    offender: [{pid,<0.117.0>},
               {id,worker},
               {mfargs,{worker,start_link,[]}},
               {restart_type,permanent},
               {shutdown,brutal_kill},
               {child_type,worker}]
** exception error: shutdown

Из вашего кода видно, что вы пытаетесь передать какой-то Time аргумент worker:init/1, так что давайте изменим start_link/0 передать метку времени:

start_link()->
    Pid = spawn_link(?MODULE,init,[os:timestamp()]),
    {ok, Pid}.

Давайте также исправим init/1 чтобы принять аргумент напрямую, а не в виде списка:

init(Time) ->
    receive
        {From,Msg} ->
            From ! {Time,Msg},
            init(Time)
end.

Перезапускаем супервизор, перекомпилируем worker, и попробуй еще раз:

10> sup:start_link().
{ok,<0.119.0>}
11> sys:trace(sup, true).
ok
12> c(worker).
worker.erl:2: Warning: export_all flag enabled - all functions will be exported
{ok,worker}
13> {ok, Child} = supervisor:start_child(sup, []).
*DBG* sup got call {start_child,[]} from <0.118.0>
*DBG* sup sent {ok,<0.127.0>} to <0.118.0>, new state {state,
                                                       {local,sup},
                                                       simple_one_for_one,
                                                       {[worker],
                                                        #{worker =>
                                                           {child,undefined,
                                                            worker,
                                                            {worker,
                                                             start_link,[]},
                                                            permanent,
                                                            brutal_kill,
                                                            worker,
                                                            [sup]}}},
                                                       {maps,
                                                        #{<0.127.0> => []}},
                                                       10,60,[],0,sup,[]}
{ok,<0.127.0>}

Похоже, это сработало. Посмотрим, согласен ли руководитель, спросив его, сколько у него детей:

14> supervisor:count_children(sup).
...
[{specs,1},{active,1},{supervisors,0},{workers,1}]

Как мы и ожидали, у него один рабочий. Наконец, давайте отправим сообщение работнику и посмотрим, как он отреагирует:

15> Child ! {self(), "are you there?"}.
{<0.118.0>,"are you there?"}
16> flush().
Shell got {{1587,394860,258120},"are you there?"}
ok

Теперь вроде все работает.

Последнее исправление - изменить модули в вашей дочерней спецификации; скорее, чем[sup] это должен быть сам модуль, [worker]. После этого изменения ваши исправленные рабочие модули представлены ниже. Вы также можете пересмотреть, хотите ли вы использоватьpermanent для детей, так как это simple_one_for_one руководитель; transientвероятно, лучший выбор, но я оставил его так, как было написано изначально. Для получения дополнительной информации рассмотрите возможность просмотра документации по поведению супервизора.

Руководитель

-module(sup).
-behaviour(supervisor).
-compile([export_all]).

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

init(_Args) ->
     RestartStrategy = {simple_one_for_one, 10, 60},
     ChildSpec = {
                  worker,
                  {worker, start_link, []},
                  permanent,
                  brutal_kill,
                  worker,
                  [worker]
                },
    {ok, {RestartStrategy,[ChildSpec]}}.

Рабочий

-module(worker).
-compile(export_all).

start_link()->
    Pid = spawn_link(?MODULE,init,[os:timestamp()]),
    {ok, Pid}.

init(Time) ->
    receive
        {From,Msg} ->
            From ! {Time,Msg},
            init(Time)
    end.

В дополнение к уже опубликованному отличному ответу я хотел бы добавить несколько моментов, чтобы объяснить поведение, наблюдаемое в вопросе.

Начальное значение в childspec - это кортеж {Mod, Fun, ArgsList} и дочерний процесс можно создать, вызвав supervisor:start_child(Supervisor, List). Супервизор запускает дочерний процесс, вызываяerlang:apply(Mod, Fun, List++ArgsList).

В этом случае начальное значение {worker, start_link, []} и ребенок был порожден вызовом supervisor:start_child(A, []). Наблюдатель попытался позвонитьerlang:apply(worker, start_link, []). Это означает, что руководитель ожидалworker:start_link/0 будет определено в workerмодуль. Ноworker модуль определен worker:start_link/1. Следовательноundef ошибка.

Учитывая определение функции

start_link([Arg]) ->
  %% do stuff

Лучше всего было создать дочерний процесс

  • пусть начальное значение в дочерней спецификации будет {worker, start_link, []}
  • вызов supervisor:start_child(A, [[Value]])

Возможно, проще определить функцию как

start_link(Arg) ->
  %% so stuff

и позвони supervisor:start_child(A, [Value])

Другие вопросы по тегам