Erlang Language
감독
수색…
소개
Erlang 프로세스를위한 유연하고 신속하며 강력한 수퍼바이저 라이브러리.
비고
경고
- 계획에서
'count'=>infinity
와 요소를restart
사용하지 마십시오.
처럼:
Childspec = #{id => foo ,start => {bar, baz, [arg1, arg2]} ,plan => [restart] ,count => infinity}.
충돌 후 프로세스가 시작되지 않으면 관리자 는 infinity
프로세스를 다시 시작합니다. 당신이 사용하는 경우 infinity
를위한 'count'
항상 사용 {restart, MiliSeconds}
에서 'plan'
대신 restart
.
- 다음과 같은 계획이있는 경우 :
Childspec1 = #{id => foo ,start => {bar, baz} ,plan => [restart,restart,delete,wait,wait, {restart, 4000}] ,count => infinity}. Childspec2 = #{id => foo ,start => {bar, baz} ,plan => [restart,restart,stop,wait, {restart, 20000}, restart] ,count => infinity}. Childspec3 = #{id => foo ,start => {bar, baz} ,plan => [restart,restart,stop,wait, {restart, 20000}, restart] ,count => 0}. Childspec4 = #{id => foo ,start => {bar, baz} ,plan => [] ,count => infinity}.
나머지 delete
의 요소 Childspec1
하고 나머지 stop
의 요소 Childspec2
평가하지 않습니다!
Childspec3
계획을 0 번 실행하고 싶습니다!
ChildSpec4
에서는 infinity
시간을 달리 계획이 없습니다!
- 당신은 사용하여 릴리스 업그레이드 할 때
release_handler
,release_handler
통화supervisor:get_callback_module/1
의 콜백 모듈을 가져 오는을 위해.
OTP <19에서get_callback_module/1
은 콜백 모듈을 제공하기 위해 감독자 내부 상태 레코드를 사용합니다. 우리의 감독이 감독 내부 상태 기록에 대해 알고하지 않습니다, 다음supervisor:get_callback_module/1
이사의 작동하지 않습니다.
좋은 소식은 OTP> = 19supervisor:get_callback_module/1
완벽하게 감독의 :)으로 작동합니다.
1> foo:start_link(). {ok,<0.105.0>} 2> supervisor:get_callback_module(foo_sup). foo 3>
다운로드
Pouriya@Jahanbakhsh ~ $ git clone https://github.com/Pouriya-Jahanbakhsh/director.git
엮다
OTP> = 19가 필요합니다 ( release_handler
사용하여 업그레이드하려는 경우).
이동 director
및 사용 rebar
또는 rebar3
.
Pouriya@Jahanbakhsh ~ $ cd director
철근
Pouriya@Jahanbakhsh ~/director $ rebar compile ==> director_test (compile) Compiled src/director.erl Pouriya@Jahanbakhsh ~/director $
철근 3
Pouriya@Jahanbakhsh ~/director $ rebar3 compile ===> Verifying dependencies... ===> Compiling director Pouriya@Jahanbakhsh ~/director $
작동 원리
감독 은 콜백 모듈 (예 : OTP 감독자)이 필요합니다.
콜백 모듈에서 함수 init/1
내 보내야합니다.
어떤 init/1
이 반환해야합니까? 잠깐, 나는 단계적으로 설명 할게.
-module(foo). -export([init/1]). init(_InitArg) -> {ok, []}.
Director 디렉토리의 foo.erl
에 위의 코드를 저장하고 Erlang 쉘로 이동하십시오.
rebar
을 사용하여 컴파일 한 경우 erl -pa ./ebin
을 사용하고 rebar3
을 사용하는 경우 rebar3 shell
을 사용 rebar3
.
Erlang/OTP 19 [erts-8.3] [source-d5c06c6] [64-bit] [smp:8:8] [async-threads:0] [hipe] [kernel-poll:false] Eshell V8.3 (abort with ^G) 1> c(foo). {ok,foo} 2> Mod = foo. foo 3> InitArg = undefined. %% i don't need it yet. undefined 4> {ok, Pid} = director:start_link(Mod, InitArg). {ok,<0.112.0>} 5>
이제 우리에게는 자녀가없는 상사가 있습니다.
좋은 소식은 디렉터 가 전체 OTP / 관리자 API를 제공하며 고급 기능과 특정 접근법도 있다는 것입니다.
5> director:which_children(Pid). %% You can use supervisor:which_children(Pid) too :) [] 6> director:count_children(Pid). %% You can use supervisor:count_children(Pid) too :) [{specs,0},{active,0},{supervisors,0},{workers,0}] 7> director:get_pids(Pid). %% You can NOT use supervisor:get_pids(Pid) because it hasn't :D []
좋아, 나는 간단한 gen_server
만들어 우리의 감독 에게 줄 것이다.
-module(bar). -behaviour(gen_server). -export([start_link/0 ,init/1 ,terminate/2]). %% i am not going to use handle_call, handle_cast ,etc. start_link() -> gen_server:start_link(?MODULE, null, []). init(_GenServerInitArg) -> {ok, state}. terminate(_Reason, _State) -> ok.
bar.erl
위의 코드를 저장하고 셸로 돌아 왔습니다.
8> c(bar). bar.erl:2: Warning: undefined callback function code_change/3 (behaviour 'gen_server') bar.erl:2: Warning: undefined callback function handle_call/3 (behaviour 'gen_server') bar.erl:2: Warning: undefined callback function handle_cast/2 (behaviour 'gen_server') bar.erl:2: Warning: undefined callback function handle_info/2 (behaviour 'gen_server') {ok,bar} %% You should define unique id for your process. 9> Id = bar_id. bar_id %% You should tell diector about start module and function for your process. %% Should be tuple {Module, Function, Args}. %% If your start function doesn't need arguments (like our example) %% just use {Module, function}. 10> start = {bar, start_link}. {bar,start_link} %% What is your plan for your process? %% I asked you some questions at the first of this README file. %% Plan should be an empty list or list with n elemenst. %% Every element can be one of %% 'restart' %% 'delete' %% 'stop' %% {'stop', Reason::term()} %% {'restart', Time::pos_integer()} %% for example my plan is: %% [restart, {restart, 5000}, delete] %% In first crash director will restart my process, %% after next crash director will restart it after 5000 mili-seconds %% and after third crash director will not restart it and will delete it 11> Plan = [restart, {restart, 5000}, delete]. [restart,{restart,5000},delete] %% What if i want to restart my process 500 times? %% Do i need a list with 500 'restart's? %% No, you just need a list with one element, I'll explain it later. 12> Childspec = #{id => Id ,start => Start ,plan => Plan}. #{id => bar_id, plan => [restart,{restart,5000},delete], start => {bar,start_link}} 13> director:start_child(Pid, Childspec). %% You can use supervisor:start_child(Pid, ChildSpec) too :) {ok,<0.160.0>} 14>
그것을 확인하자.
14> director:which_children(Pid). [{bar_id,<0.160.0>,worker,[bar]}] 15> director:count_children(Pid). [{specs,1},{active,1},{supervisors,0},{workers,1}] %% What was get_pids/1? %% It will returns all RUNNING ids with their pids. 16> director:get_pids(Pid). [{bar_id,<0.160.0>}] %% We can get Pid for specific RUNNING id too 17> {ok, BarPid1} = director:get_pid(Pid, bar_id). {ok,<0.160.0>} %% I want to kill that process 18> erlang:exit(BarPid1, kill). true %% Check all running pids again 19> director:get_pids(Pid). [{bar_id,<0.174.0>}] %% changed (restarted) %% I want to kill that process again %% and i will check children before spending time 20> {ok, BarPid2} = director:get_pid(Pid, bar_id), erlang:exit(BarPid2, kill). true 21> director:get_pids(Pid). [] 22> director:which_children(Pid). [{bar_id,restarting,worker,[bar]}] %% restarting 23> director:get_pid(Pid, bare_id). {error,not_found} %% after 5000 ms 24> director:get_pids(Pid). [{bar_id,<0.181.0>}] 25> %% Yoooohoooooo
고급 기능 에 대해 언급 했는데 그 기능 은 무엇입니까? Childspec
맵에 허용되는 다른 키를 Childspec
있습니다.
-type childspec() :: #{'id' => id() ,'start' => start() ,'plan' => plan() ,'count' => count() ,'terminate_timeout' => terminate_timeout() ,'type' => type() ,'modules' => modules() ,'append' => append()}. %% 'id' is mandatory and can be any Erlang term -type id() :: term(). %% Sometimes 'start' is optional ! just wait and read carefully -type start() :: {module(), function()} % default Args is [] | mfa(). %% I explained 'restart', 'delete' and {'restart', MiliSeconds} %% 'stop': director will crash with reason {stop, [info about process crash]}. %% {'stop', Reason}: director exactly will crash with reason Reason. %% 'wait': director will not restart process, %% but you can restart it using director:restart_child/2 and you can use supervisor:restart_child/2 too. %% fun/2: director will execute fun with 2 arguments. %% First argument is crash reason for process and second argument is restart count for process. %% Fun should return terms like other plan elements. %% Default plan is: %% [fun %% (normal, _RestartCount) -> %% delete; %% (shutdown, _RestartCount) -> %% delete; %% ({shutdown, _Reason}, _RestartCount) -> %% delete; %% (_Reason, _RestartCount) -> %% restart %% end] -type plan() :: [plan_element()] | []. -type plan_element() :: 'restart' | {'restart', pos_integer()} | 'wait' | 'stop' | {'stop', Reason::term()} | fun((Reason::term() ,RestartCount::pos_integer()) -> 'restart' | {'restart', pos_integer()} | 'wait' | 'stop' | {'stop', Reason::term()}). %% How much time you want to run plan? %% Default value of 'count' is 1. %% Again, What if i want to restart my process 500 times? %% Do i need a list with 500 'restart's? %% You just need plan ['restart'] and 'count' 500 :) -type count() :: 'infinity' | non_neg_integer(). %% How much time director should wait for process termination? %% 0 means brutal kill and director will kill your process using erlang:exit(YourProcess, kill). %% For workers default value is 1000 mili-seconds and for supervisors default value is 'infinity'. -type terminate_timeout() :: 'infinity' | non_neg_integer(). %% default is 'worker' -type type() :: 'worker' | 'supervisor'. %% Default is first element of 'start' (process start module) -type modules() :: [module()] | 'dynamic'. %% :) %% Default value is 'false' %% I'll explan it -type append() :: boolean().
foo
모듈 편집 :
-module(foo). -export([start_link/0 ,init/1]). start_link() -> director:start_link({local, foo_sup}, ?MODULE, null). init(_InitArg) -> Childspec = #{id => bar_id ,plan => [wait] ,start => {bar,start_link} ,count => 1 ,terminate_timeout => 2000}, {ok, [Childspec]}.
Erlang 쉘로 다시 이동하십시오 :
1> c(foo). {ok,foo} 2> foo:start_link(). {ok,<0.121.0>} 3> director:get_childspec(foo_sup, bar_id). {ok,#{append => false,count => 1,id => bar_id, modules => [bar], plan => [wait], start => {bar,start_link,[]}, terminate_timeout => 2000,type => worker}} 4> {ok, Pid} = director:get_pid(foo_sup, bar_id), erlang:exit(Pid, kill). true 5> director:which_children(foo_sup). [{bar_id,undefined,worker,[bar]}] %% undefined 6> director:count_children(foo_sup). [{specs,1},{active,0},{supervisors,0},{workers,1}] 7> director:get_plan(foo_sup, bar_id). {ok,[wait]} %% I can change process plan %% I killed process one time. %% If i kill it again, entire supervisor will crash with reason {reached_max_restart_plan... because 'count' is 1 %% But after changing plan, its counter will restart from 0. 8> director:change_plan(foo_sup, bar_id, [restart]). ok 9> director:get_childspec(foo_sup, bar_id). {ok,#{append => false,count => 1,id => bar_id, modules => [bar], plan => [restart], %% here start => {bar,start_link,[]}, terminate_timeout => 2000,type => worker}} 10> director:get_pids(foo_sup). [] 11> director:restart_child(foo_sup, bar_id). {ok,<0.111.0>} 12> {ok, Pid2} = director:get_pid(foo_sup, bar_id), erlang:exit(Pid2, kill). true 13> director:get_pid(foo_sup, bar_id). {ok,<0.113.0>} 14> %% Hold on마지막으로
append
키는 무엇입니까? 실제로 항상 하나의 DefaultChildspec
집니다.
14> director:get_default_childspec(foo_sup). {ok,#{count => 0,modules => [],plan => [],terminate_timeout => 0}} 15>
DefaultChildspec
은 id
및 append
키를 수락 할 수 없다는 점을 제외하고는 일반 childspec과 같습니다.
내 Childspec
에서 append
값을 true
로 변경하면 :
내 terminate_timeout
이 DefaultChildspec
terminate_timeout
에 추가됩니다.
내 count
가 DefaultChildspec
count
에 추가됩니다.
내 modules
은 DefaultChildspec
modules
에 추가됩니다.
DefaultChildspec
plan
에 내 plan
이 추가됩니다.
내가이있는 경우 그리고 start
값을 키 {ModX, FuncX, ArgsX}
에서 DefaultChildspec
하고 start
값을 키 {ModY, FunY, ArgsY}
에서 Childspec
, 최종 값이 될 것입니다 {ModY, FuncY, ArgsX ++ ArgsY}
.
그리고 내가이 마지막 경우 start
값을 키 {Mod, Func, Args}
에서 DefaultChildspec
, start
의 주요 Childspec
나를 위해 선택 사항입니다.
init/1
에서 자신의 DefaultChildspec
을 튜플의 세 번째 요소로 반환 할 수 있습니다.
foo.erl
편집 :
-module(foo). -behaviour(director). %% Yes, this is a behaviour -export([start_link/0 ,init/1]). start_link() -> director:start_link({local, foo_sup}, ?MODULE, null). init(_InitArg) -> Childspec = #{id => bar_id ,plan => [wait] ,start => {bar,start_link} ,count => 1 ,terminate_timeout => 2000}, DefaultChildspec = #{start => {bar, start_link} ,terminate_timeout => 1000 ,plan => [restart] ,count => 5}, {ok, [Childspec], DefaultChildspec}.
쉘을 다시 시작하십시오.
1> c(foo). {ok,foo} 2> foo:start_link(). {ok,<0.111.0>} 3> director:get_pids(foo_sup). [{bar_id,<0.112.0>}] 4> director:get_default_childspec(foo_sup). {ok,#{count => 5, plan => [restart], start => {bar,start_link,[]}, terminate_timeout => 1000}} 5> Childspec1 = #{id => 1, append => true}, %% Default 'plan' is [Fun], so 'plan' will be [restart] ++ [Fun] or [restart, Fun]. %% Default 'count' is 1, so 'count' will be 1 + 5 or 6. %% Args in above Childspec is [], so Args will be [] ++ [] or []. %% Default 'terminate_timeout' is 1000, so 'terminate_timeout' will be 1000 + 1000 or 2000. %% Default 'modules' is [bar], so 'modules' will be [bar] ++ [] or [bar]. 5> director:start_child(foo_sup, Childspec1). {ok,<0.116.0>} %% Test 6> director:get_childspec(foo_sup, 1). {ok,#{append => true, count => 6, id => 1, modules => [bar], plan => [restart,#Fun<director.default_plan_element_fun.2>], start => {bar,start_link,[]}, terminate_timeout => 2000, type => worker}} 7> director:get_pids(foo_sup). [{bar_id,<0.112.0>},{1,<0.116.0>}] %% I want to have 9 more children like that 8> [director:start_child(foo_sup ,#{id => Count, append => true}) || Count <- lists:seq(2, 10)]. [{ok,<0.126.0>}, {ok,<0.127.0>}, {ok,<0.128.0>}, {ok,<0.129.0>}, {ok,<0.130.0>}, {ok,<0.131.0>}, {ok,<0.132.0>}, {ok,<0.133.0>}, {ok,<0.134.0>}] 10> director:count_children(foo_sup). [{specs,11},{active,11},{supervisors,0},{workers,11}] 11>
change_default_childspec/2
사용하여 defaultChildspec
동적으로 변경할 수 있습니다!
그리고 아이들의 Childspec
을 동적으로 변경하고 append
를 true
설정할 수 true
!
하지만 코드의 다른 부분에서 코드를 변경하면 스파게티 코드가 생성됩니다.
감독을 디버깅 할 수 있습니까?
Yessssss, diorector 는 자체 디버그 기능이 있으며 표준 sys:dbg_opt/0
있습니다.
감독 은 다른 상태에서도 sasl
및 error_logger
에 유효한 로그를 보냅니다.
1> Name = {local, dname}, Mod = foo, InitArg = undefined, DbgOpts = [trace], Opts = [{debug, DbgOpts}]. [{debug,[trace]}] 2> director:start_link(Name, Mod, InitArg, Opts). {ok,<0.106.0>} 3> 3> director:count_children(dname). *DBG* director "dname" got request "count_children" from "<0.102.0>" *DBG* director "dname" sent "[{specs,1}, {active,1}, {supervisors,0}, {workers,1}]" to "<0.102.0>" [{specs,1},{active,1},{supervisors,0},{workers,1}] 4> director:change_plan(dname, bar_id, [{restart, 5000}]). *DBG* director "dname" got request "{change_plan,bar_id,[{restart,5000}]}" from "<0.102.0>" *DBG* director "dname" sent "ok" to "<0.102.0>" ok 5> {ok, Pid} = director:get_pid(dname, bar_id). *DBG* director "dname" got request "{get_pid,bar_id}" from "<0.102.0>" *DBG* director "dname" sent "{ok,<0.107.0>}" to "<0.102.0>" {ok,<0.107.0>} %% Start SASL 6> application:start(sasl). ok ... %% Log about starting SASL 7> erlang:exit(Pid, kill). *DBG* director "dname" got exit signal for pid "<0.107.0>" with reason "killed" true =SUPERVISOR REPORT==== 4-May-2017::12:37:41 === Supervisor: dname Context: child_terminated Reason: killed Offender: [{id,bar_id}, {pid,<0.107.0>}, {plan,[{restart,5000}]}, {count,1}, {count2,0}, {restart_count,0}, {mfargs,{bar,start_link,[]}}, {plan_element_index,1}, {plan_length,1}, {timer_reference,undefined}, {terminate_timeout,2000}, {extra,undefined}, {modules,[bar]}, {type,worker}, {append,false}] 8> %% After 5000 mili-seconds *DBG* director "dname" got timer event for child-id "bar_id" with timer reference "#Ref<0.0.1.176>" =PROGRESS REPORT==== 4-May-2017::12:37:46 === supervisor: dname started: [{id,bar_id}, {pid,<0.122.0>}, {plan,[{restart,5000}]}, {count,1}, {count2,1}, {restart_count,1}, {mfargs,{bar,start_link,[]}}, {plan_element_index,1}, {plan_length,1}, {timer_reference,#Ref<0.0.1.176>}, {terminate_timeout,2000}, {extra,undefined}, {modules,[bar]}, {type,worker}, {append,false}] 8>
API 문서 생성
철근 :
Pouriya@Jahanbakhsh ~/director $ rebar doc
rebar3 :
Pouriya@Jahanbakhsh ~/director $ rebar3 edoc
엘
Pouriya@Jahanbakhsh ~/director $ mkdir -p doc && erl -noshell\ -eval "edoc:file(\"./src/director.erl\", [{dir, \"./doc\"}]),init:stop()."
위 명령 중 하나를 실행 한 후 HTML 문서는 doc
디렉토리에 있어야합니다.