manbetx官方网站

[Erlang37]error/1 exit/1 exit/2 throw/1之区别。erlang应用脚本stop分析。

十月 13th, 2018  |  manbetx官方网站

1. error/1

erlang应用脚本stop分析

其实就首文章的讳应该是何许安全关闭erlang应用更加科学。

一言九鼎是系就此来定义内部错误的: Erlang内建的run time error 一共有10种:

erlang应用脚本生成

采用rebar工具,创建一个erlang节点后,
<pre>
./rebar create-node nodeid=hook_heroes
</pre>
然后以rel目录里面,执行打包命令
<pre>
./rebar generate
</pre>
会见变完整的运包,目录如下:
<pre>
bin erts-6.0 lib log releases
</pre>
bin里面,有一个开行脚论名字以及节点名字一样的,这里是hook_heroes

悬停服务之上,目前运
<pre>
./hook_heroes stop
</pre>

function_clause/case_clause/if_clause/badmatch/badarg/undef/badarith/badfun/badarity/system_limit, 比如:         

对于hook_heroes stop分析

hook_heroes stop调用如下
<pre>
%%Tell nodetool to initiate a stop
$NODETOOL stop
ES=$?
if [ “$ES” -ne 0 ]; then
exit $ES
fi
</pre>
这里的nodetool来自
<pre>
NODETOOL=”$ERTS_PATH/escript $ERTS_PATH/nodetool
</pre>
不畏erts包下的nodetool脚本,传入的参数stop
nodetool是一个escript脚本,作用就是是“Helper Script for interacting with
live nodes”

<pre>
case RestArgs of
[“getpid”] ->
io:format(“~p\n”,
[list_to_integer(rpc:call(TargetNode, os, getpid, []))]);
[“ping”] ->
io:format(“pong\n”);
[“stop”] ->
io:format(“~p\n”, [rpc:call(TargetNode, init, stop, [], 60000)]);
…….
</pre>

可观看,直接用的凡rpc:call()方法:调用TargetNode的init模块的stop方法,传入的参数为[],下面来瞧init模块的stop方法。

 1> erlang:binary_to_list(1).
    ** exception error: bad argument
       in function  binary_to_list/1
       called as binary_to_list(1)
init模块的stop()方法调用

init 模块的文档给的说是:“Coordination of System Startup”,
stop方法的注释是:
<pre>
All applications are taken down smoothly, all code is unloaded, and all
ports are closed before the system terminates
</pre>
阳就是用来系统关闭的,关键是内需省外是怎关闭系统的。

 这上面就触发了error/1,我们为足以手动触发一下。

函数入口:

<pre>
stop() -> init ! {stop,stop}, ok.
</pre>
吃init模块发送温馨发送一个{stop,stop}消息,

init自己循环接收信息
<pre>
loop(State) ->
receive
{‘EXIT’,Pid,Reason} ->
Kernel = State#state.kernel,
terminate(Pid,Kernel,Reason), %% If Pid is a Kernel pid, halt()!
loop(State);
{stop,Reason} ->
stop(Reason,State);
{From,fetch_loaded} -> %% The Loaded info is cleared in
Loaded = State#state.loaded, %% boot_loop but is handled here
From ! {init,Loaded}, %% anyway.
loop(State);
{From, {ensure_loaded, _}} ->
From ! {init, not_allowed},
loop(State);
Msg ->
loop(handle_msg(Msg,State))
end.
</pre>

配合到{stop,Reason},进入stop(Reason,State)这里调用,Reason为stop,
来起这里
<pre>
stop(Reason,State) ->
BootPid = State#state.bootpid,
{_,Progress} = State#state.status,
State1 = State#state{status = {stopping, Progress}},
clear_system(BootPid,State1),
do_stop(Reason,State1).
</pre>
首要看下clear_system函数和do_stop函数

 2> erlang:error(badarg).    
    ** exception error: bad argument        
clear_system()函数

clear_system()这里的企图就是倒闭虚拟机中的历程,只所以三只函数调用
<pre>
clear_system(BootPid,State) ->
Heart = get_heart(State#state.kernel), %A
shutdown_pids(Heart,BootPid,State), %B
unload(Heart). %C
</pre>

A和C都是当拍卖erlang启动参数heart,其含义当vm.args有证
<pre>
Heartbeat management; auto-restarts VM if it dies or becomes
unresponsive
(Disabled by default..use with caution!)
-heart
</pre>
相似情形下,不采用-heart
俺们这边只拘留shutdown_pids()怎么开的。

在意到erlang直接拿badarg这种内建的error转成重详细的bad argument,

shutdown_pids()函数

<pre>
shutdown_pids(Heart,BootPid,State) ->
Timer = shutdown_timer(State#state.flags),
catch shutdown(State#state.kernel,BootPid,Timer,State),
kill_all_pids(Heart), % Even the shutdown timer.
kill_all_ports(Heart),
flush_timout(Timer).
</pre>

此处首先关闭定时器,然后关kernel进程,然后再kill其余的过程。

关闭kernel进程
<pre>
%%
%% A kernel pid must handle the special case message
%% {‘EXIT’,Parent,Reason} and terminate upon it!
%%
shutdown_kernel_pid(Pid, BootPid, Timer, State) ->
Pid ! {‘EXIT’,BootPid,shutdown},
shutdown_loop(Pid, Timer, State, []).
</pre>

又进一步,我们吧足以利用error/1定义自己的左

什么是erlang的kernel进程?

当下句话是非同小可: A kernel pid must handle the special case message and
terminate upon it!
这就是说什么是kernel进程也?
看下bin/start.script
<pre>

{kernelProcess,heart,{heart,start,[]}},
{kernelProcess,error_logger,{error_logger,start_link,[]}},
{kernelProcess,application_controller,
{application_controller,start,
[{application,kernel,

</pre>

这些带kernelProcess标签的经过都是, 特别是application!

来自http://blog.yufeng.info/archives/1411

故supervisor_tree收到的是{‘EXIT’,BootPid,shutdown}

kill其余的过程:
<pre>
kill_all_pids(Heart) ->
case get_pids(Heart) of
[] ->
ok;
Pids ->
kill_em(Pids),
kill_all_pids(Heart) % Continue until all are really killed.
end.
</pre>
末和下去,使用的是
<pre>
exit(Pid,kill)
</pre>
向各个进程发送kill消息。

3> erlang:error("this is my own error").
   ** exception error: "this is my own error"
supervisor terminate方法

supervisor中之terminate()方法如下:
<pre>
-spec terminate(term(), state()) -> ‘ok’.

terminate(_Reason, #state{children=[Child]} = State) when
?is_simple(State) ->
terminate_dynamic_children(Child,
dynamics_db(Child#child.restart_type,
State#state.dynamics),
State#state.name);
terminate(_Reason, State) ->
terminate_children(State#state.children, State#state.name).
</pre>

分为simple_one_for_one和非simple_one_for_one两种植情况。
terminate_dynamic_children()方法:
<pre>

EStack = case Child#child.shutdown of
brutal_kill ->
?SETS:fold(fun(P, _) -> exit(P, kill) end, ok, Pids),
wait_dynamic_children(Child, Pids, Sz, undefined, EStack0);
infinity ->
?SETS:fold(fun(P, _) -> exit(P, shutdown) end, ok, Pids),
wait_dynamic_children(Child, Pids, Sz, undefined, EStack0);
Time ->
?SETS:fold(fun(P, _) -> exit(P, shutdown) end, ok, Pids),
TRef = erlang:start_timer(Time, self(), kill),
wait_dynamic_children(Child, Pids, Sz, TRef, EStack0)
end,

</pre>

得看到ChildSpec中的ShowDown字段的装置于关闭子进程的震慑:
brutal_kill:发送kill消息,这个信息是未克捕捉的。即使要worker设置了process_flag(trap_exit,
true),仍然不见面收取{‘EXIT’,_FROM,REASON}这个信息;
infinity和Time都见面朝监督的worker进程发送shutdown信号,这里worker做了
process_flag(trap_exit,
true),自然会收到{‘EXIT’,_FROM,REASON}。唯一的界别是infinity会一直等候,Time会设置一个过:如果超时了了,那么supervisor会发送kill信号,直接杀。
因地方的辨析,不难和erlang文档中于gen_server terminate()方法
<pre>
If the gen_server is part of a supervision tree and is ordered by its
supervisor to terminate, this function will be called with
Reason=shutdown if the following conditions apply:

the gen_server has been set to trap exit signals, and
the shutdown strategy as defined in the supervisor’s child specification
is an integer timeout value, not brutal_kill.
</pre>

随即无异于蹩脚,自定义之摩擦就从不吃erlang shell认出来。

supervisor何时调用terminate()方法

末尾一个题目来了,supervisor何时调用terminate()方法?之前分析到,关闭kernel进程的时刻,supervisor监控树进程会接受来自BootPid的{‘EXIT’,BootPid,shutdown}消息。我们明白supervisor实际上一个gen_server,那么去看他的handle_info()方法好了。

<pre>
-spec handle_info(term(), state()) ->
{‘noreply’, state()} | {‘stop’, ‘shutdown’, state()}.

handle_info({‘EXIT’, Pid, Reason}, State) ->
case restart_child(Pid, Reason, State) of %重启child
{ok, State1} -> %A
{noreply, State1};
{shutdown, State1} -> %B
{stop, shutdown, State1}
end;

handle_info(Msg, State) ->
error_logger:error_msg(“Supervisor received unexpected message:
pn”,
[Msg]),
{noreply, State}.
</pre>

这里代码显然还是handle_info
child发送过来的信号,调用restart_child()。在跟踪restart_child()进去,也从未看到原因:因为传播Pid并无是Child,而是BootPid,总是会倒及A分支,也就是说不会见调用terminate方法。这里陷入困境。
新生读了supervisor文档,发现还是没有terminate()方法的证实,再次陷入困境。
最后,想起supervisor实际上一个gen_server,应该去看望gen_server()文档对于terminate()方法地说明。
<pre>

Even if the gen_server is not part of a supervision tree, this function
will be called if it receives an ‘EXIT’ message from its parent. Reason
will be the same as in the ‘EXIT’ message.

</pre>
此间说明,只要gen_server收到了来自parent的’EXIT’
message,terminate()方法就是见面调用。符合之前分析地:
<pre>
{‘EXIT’,BootPid,shutdown}
</pre>
有关BootPid和SuperVisor是否是parent关系,这里小没有工夫探究:不过早晚会是,否则,顶层的sup一定要有人通知关闭啊,而且BootPid从命名来拘禁,相当有或。这里留下一个坑后填上,主要是init:start()的起步。

2. exit/1 exit/2

其它
  • 前代码中的player进程的child_spec的show_down写的是brutal_kill,这里肯定写错了;那么下关闭的时节,自然非会见调用terminate方法
  • Erlang OTP之terminate
    深入剖析即时篇稿子是基于erlang
    14A版本的,他建议下one_for_one。原因非常粗略,erlang
    14A中,supervisor的terminate()函数如下
    <pre>
    terminate(_Reason, State) ->
    terminate_children(State#state.children, State#state.name),
    ok.
    </pre>
    对于17版本,可以看看,这里没有处理单独simple_one_for_one的情况。因为simple_one_for_one和one_for_one的child信息在supervisor里面储存的是休一致的:前者child存储在dynamics属性,
    后者存储在children属性。erlang
    14A的本只有处理了children里面的child,对于simple_one_for_one的child直接没有处理。
    于当下首文章的试,我以融洽电脑上为举行了试验,确实同外的结果莫同等。

exit有internal exits 和 external
exits的区别,我们可使用exit(Pid,Reason)让别一个过程退出。

参考资料
  • Erlang OTP之terminate
    深入解析
  • erlang init
    stop浅析
  • erlang doc
  • ”Erlang supervisor 极其白痴的
    Bug“的清淤——这首文章提了下什么是erlang
    kernelProcess进程

exit/1和error/1非常相像,很多时刻可通用,便是exit语境是离,更可吃经过退出的气象,还有一个分别就是是

exit/1勿见面带来调用的stack
trace信息(方便为别进程退出时未用带好酷的调用信息,更轻量)。但是error/1会带动。

4> catch exit(test).
  {'EXIT',test}
5> catch error(test).
   {'EXIT',{test,[{erl_eval,do_apply,6,
           [{file,"erl_eval.erl"},{line,674}]},
            {erl_eval,expr,5,[{file,"erl_eval.erl"},{line,431}]},
            {shell,exprs,7,[{file,"shell.erl"},{line,686}]},
            {shell,eval_exprs,7,[{file,"shell.erl"},{line,641}]},
            {shell,eval_loop,3,[{file,"shell.erl"},{line,626}]}]}} 

3. throw/1

throw/1 它最常用配合 try…of catch
处理嵌套case(可以高速跳出),它所带的信息最少(比exit/1还不见一个’EXIT’):

6> catch throw(2+2).
4
7> catch 2+2
4     

地方2独case用的catch,都分无来结果是throw出来的,还是正常计算得到的结果,所以马上吗是援引使用try
.. of catch的故:

8> try throw(2+2) of
8> V -> {ok, V}
8> catch
8>  throw:V -> {error, V}
8> end.
{error,4} 

4. 总结

过程退出使用exit/1或exit/2,
想快跳出recursion或快速跨回Top-Level函数时用throw/1,尽量不要采取error/1,

如若需要取得调用的stack
trace信息,可以好显式的调用erlang:get_stacktrace().得到时经过时一次Exception时时之之stacktrace。

 

5. 扩展(gen_server中的几乎栽退出过程方法较)

 我们在gen_server中平安有5种退出方式,

5.1. 在init不成功时返回{stop, Reason} 退出;
5.2. 处理消息出错,或正常退出时返回{stop, Reason, NewState});
5.3. 使用exit直接退出进程;
5.4. 使用throw退出进程;
5.5. 使用error退出进程(由于我们前面讲了error主要用于定义内部错误,所以不推荐使用)。

面前少种植是显而易见的,关键是后三栽艺术退出以及前者的分。

事先来调停一调理{stop, Reason, NewState},直接exit, 直接throw三者的界别。

https://github.com/erlang/otp/blob/maint/lib/stdlib/src/gen_server.erl#L601

try_dispatch(Mod, Func, Msg, State) ->
    try
    {ok, Mod:Func(Msg, State)}
    catch
    throw:R ->
        {ok, R};
    error:R ->
        Stacktrace = erlang:get_stacktrace(),
        {'EXIT', {R, Stacktrace}, {R, Stacktrace}};
    exit:R ->
        Stacktrace = erlang:get_stacktrace(),
        {'EXIT', R, {R, Stacktrace}}
    end.

以stop就是健康退出,不带来stack
trace信息(他当就是没有crash,也未尝stack信息)

使用exit退出,它带了stack trace信息。

下throw退出,它并未拉动stack
trace信息,且只要throw出来的term要是符合gen_server标准,比如

{noreply, NewState}
{reply, ok, NewState}
{stop, normal, NewState}

 

  

知道了上述区别,就老大爱选择了:

1.exit啊是用来不可预料的错,需要回到其的stack
trace信息用来记录,所以不引进以gen_server中主动调用exit;

2.throw可长足回答此次信息的拍卖,简化代码的嵌套case逻辑,不过若是顾,throw出来的定是一个入gen_server标准的物,不然会报错。

3.健康不过预料的逻辑都尚且施用{stop,Reason, NewState}处理。 

俺们再度深入一些:

当我们stop时之Reason不是 normal | shutdown |
{shutdown,term()} 时会在kernel log中打印日志

 

Notice that for any other reason than normal, shutdown,
or {shutdown,Term}, the gen_server process is assumed to terminate
because of an error and an error report is issued
using error_logger:format/2. 

论这样:

=ERROR REPORT==== 31-Oct-2016::15:10:44 ===
** Generic server test_throw_exit terminating
** Last message in was go
** When Server state == {state}
** Reason for termination ==
** not_shutdown_term

 

故,如果我们在正常退出或shutdown时不需那么无异排烦人的log,就管reason定义为三者中之均等种植就是实施了。

切切实实源码可见:
https://github.com/erlang/otp/blob/maint/lib/stdlib/src/gen_server.erl#L809

总结:

  1. 在gen_server中正常退出,请回复{stop, Reason, NewState},

2.
要是是设迅速结束此次消息之淡出可以使throw(term()),其中term()符合gen_server规范(和健康返回的值一样),

  1. 健康逻辑之中未引进使用exit/error来处理,分带多余的stack
    trace信息。 

 

6. 参考资料:

  Erlang官方文档:http://erlang.org/doc/reference\_manual/errors.html

  Learnyousomeerlang关于Exceptions的介绍: http://learnyousomeerlang.com/errors-and-exceptions

图片 1

标签:, , , ,

Your Comments

近期评论

    功能


    网站地图xml地图