Почему мой супервизор терпит неудачу на 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])