erlang應用腳本stop分析

erlang應用腳本stop分析

其實這篇文章的名字應該是如何安全關閉erlang應用更加科學。

erlang應用腳本生成

使用rebar工具毕谴,創(chuàng)建一個erlang節(jié)點后,
<pre>
./rebar create-node nodeid=hook_heroes
</pre>
然后在rel目錄里面,執(zhí)行打包命令
<pre>
./rebar generate
</pre>
會生成完整的應用包民轴,目錄如下:
<pre>
bin erts-6.0 lib log releases
</pre>
bin里面,有一個啟動腳本名字和節(jié)點名字一樣的球订,這里是hook_heroes

停止服務的時候后裸,目前使用
<pre>
./hook_heroes stop
</pre>

對于hook_heroes stop分析

hook_heroes stop調(diào)用如下
<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腳本,傳入的參數(shù)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()方法:調(diào)用TargetNode的init模塊的stop方法,傳入的參數(shù)為[]开睡,下面來看看init模塊的stop方法因苹。

init模塊的stop()方法調(diào)用

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>
顯然就是用來系統(tǒng)關閉的篇恒,關鍵是需要看看他是怎么關閉系統(tǒng)的扶檐。

函數(shù)入口:

<pre>
stop() -> init ! {stop,stop}, ok.
</pre>
給init模塊發(fā)送自己發(fā)送一個{stop,stop}消息,

init自己循環(huán)接收消息
<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)這里調(diào)用胁艰,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函數(shù)和do_stop函數(shù)

clear_system()函數(shù)

clear_system()這里的作用就是關閉虛擬機中的進程款筑,只用三個函數(shù)調(diào)用
<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啟動參數(shù)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()怎么做的奈梳。

shutdown_pids()函數(shù)

<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>

什么是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>
向各個進程發(fā)送kill消息艰匙。

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:發(fā)送kill消息,這個消息是不能捕捉的员凝。即使如果worker設置了process_flag(trap_exit, true),仍然不會收到{'EXIT',_FROM,REASON}這個消息署驻;
infinity和Time都會向監(jiān)督的worker進程發(fā)送shutdown信號,這里worker做了 process_flag(trap_exit, true),自然會收到{'EXIT',_FROM,REASON}旺上。唯一的區(qū)別是infinity會一直等待瓶蚂,Time會設置一個超時:如果超時過了,那么supervisor會發(fā)送kill信號宣吱,直接殺死窃这。
根據(jù)上面的分析,不難和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>

supervisor何時調(diào)用terminate()方法

最后一個問題來了,supervisor何時調(diào)用terminate()方法征候?之前分析到杭攻,關閉kernel進程的時候,supervisor監(jiān)控樹進程會收到來自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發(fā)送過來的信號跑揉,調(diào)用restart_child()锅睛。在跟蹤restart_child()進去,也沒有看出原因:因為傳入Pid并不是Child,而是BootPid历谍,總是會走到A分支现拒,也就是說不會調(diào)用terminate方法。這里陷入困境扮饶。
后來翻閱了supervisor文檔具练,發(fā)現(xiàn)居然沒有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()方法就會調(diào)用奥帘。符合之前分析地:
<pre>
{'EXIT',BootPid,shutdown}
</pre>
至于BootPid和SuperVisor是否是parent關系,這里暫時沒時間探究:不過一定會是仪召,否則寨蹋,頂層的sup一定要有人通知關閉啊,而且BootPid從命名來看扔茅,相當有可能已旧。這里留一個坑后面填上,主要是init:start()的啟動召娜。

其它
  • 之前代碼中的player進程的child_spec的show_down寫的是brutal_kill运褪,這里顯然寫錯了;那么應用關閉的時候,自然不會調(diào)用terminate方法
  • Erlang OTP之terminate 深入分析這篇文章是基于erlang 14A版本的秸讹,他建議使用one_for_one檀咙。原因很簡單,erlang 14A中,supervisor的terminate()函數(shù)如下
    <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直接沒有處理氧秘。
    對于這篇文章的實驗年鸳,我在自己電腦上也做了實驗,確實和他的結果不一致丸相。
參考資料
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末搔确,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子灭忠,更是在濱河造成了極大的恐慌膳算,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弛作,死亡現(xiàn)場離奇詭異涕蜂,居然都是意外死亡,警方通過查閱死者的電腦和手機映琳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門机隙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人萨西,你說我怎么就攤上這事有鹿。” “怎么了谎脯?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵葱跋,是天一觀的道長。 經(jīng)常有香客問我源梭,道長娱俺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任废麻,我火速辦了婚禮荠卷,結果婚禮上,老公的妹妹穿的比我還像新娘脑溢。我一直安慰自己僵朗,他們只是感情好赖欣,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著验庙,像睡著了一般顶吮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上粪薛,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天悴了,我揣著相機與錄音,去河邊找鬼违寿。 笑死湃交,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的藤巢。 我是一名探鬼主播搞莺,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼掂咒!你這毒婦竟也來了才沧?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤绍刮,失蹤者是張志新(化名)和其女友劉穎温圆,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孩革,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡岁歉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了膝蜈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锅移。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖彬檀,靈堂內(nèi)的尸體忽然破棺而出帆啃,到底是詐尸還是另有隱情瞬女,我是刑警寧澤窍帝,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站诽偷,受9級特大地震影響坤学,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜报慕,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一深浮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧眠冈,春花似錦飞苇、人聲如沸菌瘫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雨让。三九已至,卻和暖如春忿等,著一層夾襖步出監(jiān)牢的瞬間栖忠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工贸街, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留庵寞,地道東北人。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓薛匪,卻偏偏與公主長得像捐川,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子逸尖,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容

  • erlang應用腳本stop分析 其實這篇文章的名字應該是如何安全關閉erlang應用更加科學属拾。 erlang應用...
    randyjia閱讀 1,229評論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)冷溶,斷路器渐白,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • supervisor 是由python語言編寫、基于linux操作系統(tǒng)的一款服務器管理工具逞频,用以監(jiān)控服務器的運行纯衍,...
    每次哭都笑著奔跑閱讀 6,294評論 6 14
  • 下標語法概念: 類和結構體以及枚舉能夠聲明下標快捷訪問集合中的成員。例如數(shù)組someArray[index]或者字...
    成功的失敗者閱讀 991評論 0 0
  • 夏季的高原無疑是安靜的苗胀,靜的讓人心煩意亂襟诸、讓人撕心裂肺、讓人不知所措基协,那靜有種說不出的味道歌亲,是沉到心底里,掏空了細...
    木皮叔閱讀 517評論 1 6