为什么我的主管使用 undef 在 start_child 上失败了?
Why does my supervisor fail on start_child with undef?
我正在尝试 运行 simple_one_for_one supervisor
,其中 supervisor
和 worker
被放置在不同的模块中,我在使用 [=18 时不断收到以下错误=]:
>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,[]).
我可以清楚地看到问题是在尝试添加 child.Somehow 时未正确调用 init
函数,但我不明白为什么。(不匹配)
我试过在ChildSpec
的MFA
的A
中加一个参数没有用
你的代码有很多问题。
sup:start_link/0
的return值错误;你 returning Pid 而不是 {ok, Pid}
.
虽然不是真的不正确,但您使用的是 supervisor:start_link/2
,它没有注册主管名称。有名字很方便,所以最好使用 supervisor:start_link/3
:
supervisor:start_link({local, ?MODULE}, ?MODULE, []),
这将模块名称与其进程 ID 相关联,允许您在 shell 命令中使用进程名称,而不是使用 pid 变量。
- 您在
sup:start_link/0
中有一个 io:format/2
调用,大概是为了调试。一种更好的调试方法是在启动 sup
主管后从 shell 调用 sys:trace(sup, true)
。您也可以从 shell 中关闭它,方法是指定 false
而不是 true
作为第二个参数。
解决上述问题后,使用 sup:start_link/0
的以下定义:
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
让我们重新编译,启动 supervisor,然后编译 worker
(修复其语法错误后),然后在我们尝试启动 child:
时跟踪 supervisor
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:start_link/0
启动 worker
时死亡([]
表示参数为零)。 child 规范告诉 sup
以这种方式启动它,因为它包含
{worker, start_link, []}
我们通过 supervisor:start_child(sup, [])
启动了 child。对于 simple_one_for_one
child,发送到其启动函数的参数由 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
return 是一个 pid,但 worker:start_link/0
期望它是 return {ok, Pid}
。让我们修复它,并将 return 值修复为 {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,[],[]}]}}
好吧,这次supervisor真的启动了child,但是马上就死掉了,因为它试图调用worker:init/0
,但只定义了worker:init/1
。因为 child 立即死亡,监督者根据其重启策略反复尝试启动它:
RestartStrategy = {simple_one_for_one, 10, 60},
因为这是一个硬错误,所以每次都会立即失败,并且主管在 60 秒或更短时间内重启 10 次失败后死亡,就像它应该的那样:
=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.
让我们重启supervisor,重新编译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>}
看起来成功了。看主管同意不同意,问它有多少children:
14> supervisor:count_children(sup).
...
[{specs,1},{active,1},{supervisors,0},{workers,1}]
正如我们所料,它只有一名工人。最后,让我们给 worker 发送一条消息,看看它是否按预期响应:
15> Child ! {self(), "are you there?"}.
{<0.118.0>,"are you there?"}
16> flush().
Shell got {{1587,394860,258120},"are you there?"}
ok
现在一切似乎都奏效了。
最后一个解决方法是更改 child 规范中的模块;而不是 [sup]
,它应该是模块本身,[worker]
。通过该更改,您修改后的工作模块如下所示。您可能还需要重新考虑是否要为 children 使用 permanent
,因为这是 simple_one_for_one
主管; transient
可能是更好的选择,但我保留了最初编写的内容。考虑查看 Supervisor Behavior documentation 以获取更多信息。
主管
-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
模块中定义 worker:start_link/0
。但是 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])
我正在尝试 运行 simple_one_for_one supervisor
,其中 supervisor
和 worker
被放置在不同的模块中,我在使用 [=18 时不断收到以下错误=]:
>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,[]).
我可以清楚地看到问题是在尝试添加 child.Somehow 时未正确调用 init
函数,但我不明白为什么。(不匹配)
我试过在ChildSpec
的MFA
的A
中加一个参数没有用
你的代码有很多问题。
sup:start_link/0
的return值错误;你 returning Pid 而不是{ok, Pid}
.虽然不是真的不正确,但您使用的是
supervisor:start_link/2
,它没有注册主管名称。有名字很方便,所以最好使用supervisor:start_link/3
:supervisor:start_link({local, ?MODULE}, ?MODULE, []),
这将模块名称与其进程 ID 相关联,允许您在 shell 命令中使用进程名称,而不是使用 pid 变量。
- 您在
sup:start_link/0
中有一个io:format/2
调用,大概是为了调试。一种更好的调试方法是在启动sup
主管后从 shell 调用sys:trace(sup, true)
。您也可以从 shell 中关闭它,方法是指定false
而不是true
作为第二个参数。
解决上述问题后,使用 sup:start_link/0
的以下定义:
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
让我们重新编译,启动 supervisor,然后编译 worker
(修复其语法错误后),然后在我们尝试启动 child:
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:start_link/0
启动 worker
时死亡([]
表示参数为零)。 child 规范告诉 sup
以这种方式启动它,因为它包含
{worker, start_link, []}
我们通过 supervisor:start_child(sup, [])
启动了 child。对于 simple_one_for_one
child,发送到其启动函数的参数由 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
return 是一个 pid,但 worker:start_link/0
期望它是 return {ok, Pid}
。让我们修复它,并将 return 值修复为 {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,[],[]}]}}
好吧,这次supervisor真的启动了child,但是马上就死掉了,因为它试图调用worker:init/0
,但只定义了worker:init/1
。因为 child 立即死亡,监督者根据其重启策略反复尝试启动它:
RestartStrategy = {simple_one_for_one, 10, 60},
因为这是一个硬错误,所以每次都会立即失败,并且主管在 60 秒或更短时间内重启 10 次失败后死亡,就像它应该的那样:
=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.
让我们重启supervisor,重新编译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>}
看起来成功了。看主管同意不同意,问它有多少children:
14> supervisor:count_children(sup).
...
[{specs,1},{active,1},{supervisors,0},{workers,1}]
正如我们所料,它只有一名工人。最后,让我们给 worker 发送一条消息,看看它是否按预期响应:
15> Child ! {self(), "are you there?"}.
{<0.118.0>,"are you there?"}
16> flush().
Shell got {{1587,394860,258120},"are you there?"}
ok
现在一切似乎都奏效了。
最后一个解决方法是更改 child 规范中的模块;而不是 [sup]
,它应该是模块本身,[worker]
。通过该更改,您修改后的工作模块如下所示。您可能还需要重新考虑是否要为 children 使用 permanent
,因为这是 simple_one_for_one
主管; transient
可能是更好的选择,但我保留了最初编写的内容。考虑查看 Supervisor Behavior documentation 以获取更多信息。
主管
-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
模块中定义 worker:start_link/0
。但是 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])