erlang 공부 : Who Supervises The Supervisors
- 프로그래밍 언어/erlang
- 2023. 12. 16.
참조
1. From Bad To Good
이전에 구현했던 Supervisor는 process_flag(trap_exit, treu)를 이용해 시스템 프로세스가 되어서 워커 프로세스의 종료 신호를 받고 되살리는 작업을 했었다. 그런데 이 단순한 Supervisor는 근본적인 문제 두 가지를 가지고 있다.
- 단순 통신 오류로 Retry 한번만 다시 하면 사용할 수 있는 프로세스인데, 죽이고 처음부터 시작해야 함.
- 하나의 Supervisor는 하나의 워크 프로세스만 관리할 수 있음.
OTP에서 제공해주는 슈퍼바이저는 위 단점을 해결해준다.
2. Supervisor Concepts
슈퍼바이저는 슈퍼바이저 - 워커 구조로 이루어져있다. 각각의 개념은 다음과 같아.
- 슈퍼바이저
- 워커가 죽었을 때 살려주는 프로세스
- 슈퍼바이저는 워커 + 다른 슈퍼바이저를 감독할 수 있음.
- 워커
- 실제 작업을 진행하는 프로세스
- 어떤 계층 구조도 가지지 않음. (자신의 밑에 있는 프로세스가 없음)
2.1 왜 모든 프로세스는 관리되어야 할까?
erlang에서는 슈퍼바이저를 이용해서 대부분의 프로세스를 관리하려고 한다. 그 이유는 무엇일까? 크게 두 가지 이유가 있다.
2.1.1 모르는 프로세스는 메모리 누수의 원인
슈퍼바이저에 의해서 관리되지 않는 프로세스는 '살아있는지 죽어있는지'를 명확히 알 수 없는 상태가 된다. 또한 그런 프로세스는 '어디서 생성되었는지' 알 수 없는 프로세스가 된다. 어떻게 생기는지도 모르는 프로세스가 죽었는지 살았는지도 모르는 상태가 지속되면, 결과적으로 '메모리 누수'로 이어질 수 있다.
2.1.2. 깔끔하게 종료할 수 있음.
슈퍼바이저 트리에 의해서 모든 프로세스가 관리될 때 어플리케이션을 종료하고 싶다면, Root Supervisor에게 종료 요청을 보내면 된다. Root Supervisor는 각자 자식 Supervisor에게 재귀적으로 종료 요청을 하게 될 것이고, 이를 통해서 편리하게 종료할 수 있게 된다.
3. Using Supervisors
Suervisor는 오로지 하나의 콜백함수 init/1을 사용한다. 그런데 init/1 호출 결과로 복잡한 값이 반환된다.
% 반환값
{ok, {{one_for_all, 5, 60},
[{fake_id,
{fake_mod, start_link, [SomeArg]},
permanent,
5000,
worker,
[fake_mod]},
{other_id,
{event_manager_mod, start_link, []},
transient,
infinity,
worker,
dynamic}]}}.
% 일반화된 반환값.
{ok, {{RestartStrategy, MaxRestart, MaxTime},
[ChildSpecs]}}.
ChildSpecs은 자식 프로세스들을 위한 스펙이다. 주로 봐야할 부분은 RestartStrategy, MaxRestart, MaxTime이다.
3.1 Restart Strategy
- Restart Strategy는 one_for_one, rest_for_one, one_for_all, simple_on_for_one이 있다. 각각에 대해서 살펴보자.
3.1.1 one_for_one
- 슈퍼바이저 밑에 여러 프로세스가 관리되고 있을 때, 하나의 프로세스가 죽는 경우, 죽은 프로세스만 재기동한다.
- 다른 종류의 코드를 실행하는 자식 프로세스가 만들어 질 수 있음.
3.1.2 one_for_all
- 하나의 슈퍼바이저 아래에 있는 프로세스가 하나라도 죽는 경우, 모든 자식 프로세스를 재기동한다.
- 다른 종류의 코드를 실행하는 자식 프로세스가 만들어 질 수 있음.
3.1.3 rest_for_one
- 하나의 슈퍼바이저 아래에 있는 프로세스 하나가 죽은 경우, 해당 프로세스가 생성된 이후에 생성된 모든 프로세스도 함께 재기동된다.
- 다른 종류의 코드를 실행하는 자식 프로세스가 만들어 질 수 있음.
3.1.4 simple_one_for_one
- 공통된 코드를 실행하는 자식 프로세스가 만들어 질 수 있음.
- 하나의 자식 프로세스가 실패할 경우, 그 녀석만 재기동 함.
one_for_one / simple_one_for_one의 차이는 다음과 같다.
- one_for_one은 서로 다른 코드를 가진 자식 프로세스를 순서대로 시작하여 list로 가지고 있음.
- simple_one_for_one은 모든 자식이 동일흔 코드를 실행하고, dict를 이용해 데이터를 보관함.
만약 많은 자식 프로세스를 가진 경우라면, simple_one_for_one이 자식 프로세스의 종료로부터 더 빠르게 복구한다.
3.2 Restart Limit
MaxRestart, MaxTime을 이용해서 자식 프로세스의 재시작을 제한할 수 있다.
- MaxTime(초 단위) 내에 MaxRestart보다 많은 재시작이 발생하면, 해당 슈퍼바이저는 종료된다.
- 종료된 슈퍼바이저의 부모 슈퍼바이저는 자식 슈퍼바이저를 다시 살리려고 한다.
3.3 Child Specifications.
Child Spec은 Supervisor에 의해서 관리되는 자식 프로세스들의 설정값을 의미한다. Supervisor는 자식 프로세스가 죽었을 때, Child Spec을 참고해서 자식 프로세스를 재기동한다.
[{fake_id, % Child ID
{fake_mod, start_link, [SomeArg]}, % Start Func
permanent, % Restart
5000, % Shutdown
worker, % Process Type
[fake_mod]}, % Process Module
{other_id,
{event_manager_mod, start_link, []},
transient,
infinity,
worker,
dynamic}]
위는 Child Spec의 예시다. 엄청 복잡한 값들이 들어가있는데, 이것을 튜플 방식으로 풀어보면 다음과 같다.
{ChildId, StartFunc, Restart, Shutdown, Type, Modules}.
SuperVisor는 위 튜플에 있는 값들을 참고해서 자식 프로세스를 재기동할 지를 결정하고 필요한 경우 재생성한다. 그렇다면 각 요소들은 무엇을 나타내는 것일까?
ChildId
- 슈퍼바이저가 내부적으로 사용하는 자식 프로세스의 이름. 디버깅 목적으로 주로 사용함.
- 슈퍼바이저가 내부적으로 관리하는 List에 저장되고 이곳에 접근 가능함.
StartFunc
- 자식 프로세스를 시작하는 방법을 알려주는 튜플임. MFA 형식으로 되어있음.
- Start Function은 항상 OTP와 호환되고, 호출자와 Link 되어야함. → 반드시 gen_*:start_link()을 사용해야함.
Restart
- 자식이 종료되었을 때 Supervisor가 어떻게 동작할지 결정함. 아래 세 가지 값을 가질 수 있음.
- permanent : 항상 다시 시작함. (초기에 수작업으로 작성했던 코드 생각하면 됨). 오래 살아야 하는 프로세스.
- temporary : 절대로 다시 시작하지 않음. (수명이 짧고, 실패가 예상되며, 의존하는 코드가 거의 없는 프로세스)
- transient : 정상적으로 종료되면 다시 시작되지 않음. 비정상적으로 종료되는 경우 재시작됨.
자식 프로세스의 Restart는 SuperVisord의 Restart 전략과 함께 사용할 수 있다. 예를 들어 one_for_all 전략을 선택했을 떄, temporary Process가 비정상적으로 종료된 경우를 고려해보자. 이 때 temporary Process가 종료되어도 나머지 프로세스가 one_for_all 전략을 따라 재기동 되지는 않는다.
Shutdown.
Shutdown은 Supervisor가 shutdown 메세지를 받았을 때 Supervisor가 어떻게 동작할지를 정하는 항목이다.
앞서서 Root Supervisor가 Shutdown 메세지를 받으면 재귀적으로 자식 노드들을 Shutdown 시킨다고 했다. 정확하게는 Root Supervisor가 Shutdown 메세지를 받으면 각 Pid에 exit(ChildPid, Shutdown)을 호출한다.
- Worker + trap exit 인 경우 → 자체 terminate() 함수 호출함.
- 그렇지 않은 경우 → 그냥 종료됨.
- Supervisor인 경우 → 자식에게 전파함.
이 때 각 경우에는 위와 같이 동작한다.
여기서 Shutdown으로 정할 수 있는 값은 다음과 같다.
- 정수: 정수 (ms)만큼 자식 프로세스가 종료될 때까지 기다린다. 이 시간이 지나도 종료되지 않으면 강제로 종료시킴.
- brutal_kill : 기다리지 않고 바로 자식 프로세스를 종료시킴. exit(Pid, kill).
- infinity : 자식 프로세스가 종료될 때까지 무한정 기다림.
Supervision Tree에서 Shutdown을 정수로 설정할 때는 적절한 값을 잘 설정해야한다. 예를 들어 다음 경우를 고려해보자.
A Supervisor(1000) → B Supervisor(5000)의 트리를 가질 때, B Supervisor의 자식들이 모두 종료되는데 4000이 걸린다고 가정해보자. 그런데 A Supervisor는 1000만 기다려주기 때문에 B Supervisor는 강제 종료된다.
Type
- worker
- supervisor
자식 프로세스가 어떤 타입인지 알려준다. 위 두가지 값을 가진다.
Modules
자식 프로세스가 사용하는 모듈들을 의미한다. 한 가지 이상의 값이 올 수 있다.
3.3.1 최신 업데이트
{#{strategy => RestartStrategy, intensity => MaxRestart, period => MaxTime},
[#{id => ChildId,
start => StartFunc,
restart => Restart,
shutdown => Shutdown,
type => Type,
modules => Module}}.
OTP 18.0부터는 Map의 형태로 값을 전달할 수 있다고 한다.
4. Testing it Out
Supervisor를 적용해보기 위해서 Supervisor를 적용할만한 프로세스 코드를 먼저 작성한다. 코드의 내용은 다음과 같다.
- Musician 모듈에서 연주할 악기를 선택해서 연주자를 만들 수 있음.
- 각 연주자는 연주 스킬이 Good / Bad인 경우가 있음.
- Good인 경우, 연주가 한번도 실패하지 않음.
- Bad인 경우, 연주가 실패함.
- 연주자 프로세스는 만들어지자마자 음악을 연주함.
- 반응하는 API는 stop에만 반응함.
- 종료 조건은 다음이 존재함.
- normal
- bad_note
- shutdown
-module(p_musician).
-behavior(gen_server).
-record(state,
{
name="",
role,
skill=good}).
-define(DELAY, 750).
%% API
-export([start/2, start_link/2, stop/1, init/1, code_change/3, terminate/2]).
-export([handle_call/3, handle_cast/2, handle_info/2]).
start_link(Role, Skill) ->
gen_server:start_link({local, Role}, ?MODULE, [Role, Skill], []).
start(Role, Skill) ->
gen_server:start({local, Role}, ?MODULE, [Role, Skill], []).
stop(Role) -> gen_server:call(Role, stop).
init([Role, Skill]) ->
process_flag(trap_exit, true),
random:seed(now()),
TimeToPlay = random:uniform(3000),
Name = pick_name(),
StrRole = atom_to_list(Role),
io:format("Musician ~s, playing the ~s entered the room~n", [Name, StrRole]),
{ok, #state{name=Name, role=StrRole, skill=Skill}, TimeToPlay}.
handle_call(stop, _From, S) ->
{stop, normal, ok, S};
handle_call(_Message, _From, S) ->
{noreply, S, ?DELAY}.
handle_cast(_Message, S) ->
{noreply, S, ?DELAY}.
handle_info(timeout, S = #state{name=N, skill=good}) ->
print_ok(N),
{noreply, S, ?DELAY};
handle_info(timeout, S = #state{name=N, skill=bad}) ->
case rand:uniform(5) of
1 ->
io:format("~s played a false note. Uh oh. ~n", [N]),
{stop, bad_note, S};
_ ->
print_ok(N),
{noreply, S, ?DELAY}
end;
handle_info(_Message, S) ->
{noreply, S, ?DELAY}.
terminate(normal, S) ->
io:format("~s left the room (~s). ~n", [S#state.name, S#state.role]);
terminate(bad_note, S) ->
io:format("~s sucks! kicked that member out of the band! (~s). ~n)",[S#state.name, S#state.role]);
terminate(shutdown, S) ->
io:format("The manager is mad and fired the whole band!
~s just got back to playing in the subway. ~n", [S#state.name]);
terminate(_Reason, S) ->
io:format("~s has been kicked out (~s). ~n", [S#state.name, S#state.role]).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
% Private function
print_ok(N) ->
io:format("~s produced sound!~n", [N]).
pick_name() ->
lists:nth(rand:uniform(10), firstnames())
++ " " ++
lists:nth(rand:uniform(10), lastnames()).
firstnames() ->
["Valerie", "Arnold", "Carlos", "Dorothy", "Keesha",
"Phoebe", "Ralphie", "Tim", "Wanda", "Janet"].
lastnames() ->
["Frizzle", "Perlstein", "Ramon", "Ann", "Franklin",
"Terese", "Tennelli", "Jamal", "Li", "Perlstein"].
작성된 코드는 위와 같다. 주요 동작 방식은 다음과 같다
- init() 시에 TimeToPlay라는 값을 Timeout으로 주었다. 이 시간동안 메세지를 받지 못하면, 해당 프로세스는 Timeout이 발생한다.
- Timeout이 발생하면 handle_info() 메서드가 호출된다. 이 때 호출사유는 timeout으로 전달된다.
- timeout으로 handle_info()가 호출되었을 때, Good인 경우에는 계속 연주를 하도록 하고 Timeout 시간을 다시 한번 전달한다
- timeout으로 handle_info()가 호출되었을 때, Bad인 경우에는 1~5중에 1이 나오는 경우에는 bad_note로 종료하도록 하고 나머지는 계속 연주하도록 한다.
- 종료 코드는 normal, shutdown, bad_note가 있고, 각각에 따라서 서로 다르게 종료한다.
이 때 테스트 해볼 수 있는 코드는 다음과 같다.
1> c(musician).
2> p_musician:start_link(base, bad).
Musician Ralphie Terese, playing the base entered the room
self : <0.176.0>
{ok,<0.176.0>}
Ralphie Terese produced sound!
Ralphie Terese produced sound!
Ralphie Terese produced sound!
Carlos Perlstein played a false note. Uh oh.
Carlos Perlstein sucks! kicked that member out of the band! (base).
)=ERROR REPORT==== 16-Dec-2023::00:20:48.556000 ===
컴파일 후에 프로세스를 생성해주면 자동으로 연주를 한다. 그런데 여기서 한 가지 문제점은 연주자가 죽었을 때, 누군가 살려줄 사람이 없다는 것이다. 잘못된 연주를 해서 연주가 프로세스가 종료(해고)되었을 때, 새로운 연주자 프로세스를 만들어줄 누군가가 필요하다.
이것을 OTP 프레임워크에서는 Supervisor를 통해서 제공해준다.
5. BandSupervisor
앞서 작성한 Musician은 불안정하다. 왜냐하면 프로세스가 죽었을 때 재기동해주는 Supervisor가 없기 때문이다. 여기서는 OTP 프레임워크가 제공하는 OTP Behaviour를 이용해서 Band Process를 재기동해주는 Supervisor 코드를 작성하고자 한다.
-behavior(supervisor).
start_link(Type) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, Type).
% RestartStrategy, MaxRestart, MaxTime
init(lenient) ->
init({one_for_one, 3, 60});
init(angry) ->
init({rest_for_one, 2, 60});
init(jerk) ->
init({one_for_all, 1, 60});
먼저 init(Type) 코드를 작성한다. 어떤 타입이 전달되느냐에 따라 재시작 전략 및 재시작 가능 횟수가 달라진다.
- lenient : 프로세스가 실패하면 그 프로세스만 재기동한다.
- 60초동안 4번 실패 시, 전체 실패로 처리함.
- angry : 프로세스 하나가 죽은 경우, 해당 프로세스가 생성된 이후에 생성된 프로세스들도 함께 재기동된다.
- 60초 동안 2번 실패 시, 전체 실패로 처리함.
- jerk : 하나라도 죽으면 모두 재기동한다.
각 모드에 따라 Supervisor는 상이한 재기동 전략을 가진다. 그리고 이제 Supervisor가 관리할 자식 프로세스의 스펙을 관리해야한다.
get_child_spec() ->
[
#{
id => singer,
start => {p_musician, start_link, [singer, good]},
restart => permanent,
shutdown => 1000,
type => worker,
modules => [p_musician]
},
#{
id => bass,
start => {p_musician, start_link, [bass, bad]},
restart => temporary,
shutdown => 1000,
type => worker,
modules => [p_musician]
},
#{
id => drum,
start => {p_musician, start_link, [drum, bad]},
restart => transient,
shutdown => 1000,
type => worker,
modules => [p_musician]
}].
start 키를 살펴보면 start_link()를 수행할 때 전달되는 Argument에 따라 서로 다르게 동작하도록 구성되었다는 것을 알 수 있다.
- 첫번째 → singer 프로세스를 생성.
- 두번째 → bass 프로세스를 생성
- 세번째 → drum 프로세스를 생성
각 프로세스마다 상이한 Argument를 가지기 때문에 어떤 프로세스는 매우 빈번히 실패하게 될 것이다.
init({RestartStrategy, MaxRestart, MaxTime}) ->
SupervisorFlags =
#{
strategy => RestartStrategy,
intensity => MaxRestart,
period => MaxTime
},
ChildSpec = get_child_spec(),
{ok, {SupervisorFlags, ChildSpec}}.
그리고 위 함수를 조합해서 값을 반환해야하는 init() 메서드를 완성한다.
5> band_supervisor:start_link(angry).
Musician Dorothy Frizzle, playing the singer entered the room
Musician Arnold Li, playing the bass entered the room
Musician Ralphie Perlstein, playing the drum entered the room
Musician Carlos Perlstein, playing the keytar entered the room
... <snip> ...
Ralphie Perlstein sucks! kicked that member out of the band! (drum)
...
The manager is mad and fired the whole band! Carlos Perlstein just got back to playing in the subway
이제 supervisor를 시작해보면, 문제가 있어서 추방된 멤버 Ralphie Perlstein이 존재한다. 추방되었다는 것은 musician 모듈 관점에서는 gen_server가 Timeout으로 종료된 것을 의미한다. 그러나 Supervisor가 musician이 죽은 것을 감지하고 새로운 musician을 생성해서 1분 동안 4번 실패할 때 까지는 계속 프로세스를 살려주는 것을 확인할 수 있다.
6. Dynamic Supervision.
위에서 살펴본 방법은 Static한 방법이다. 코드에 Supervisor가 관리할 프로세스를 정의하고, 어플리케이션이 시작되면 그 설정값을 읽어서 관리하는 방식이다.
런타임 시점에 Supervisor에 의해서 관리되어야 하는 프로세스가 존재할 수도 있다. 예를 들어 요청이 올 때 마다 요청에 응답하기 위한 프로세스가 있다면, 이런 프로세스가 런타임 시점에 동적으로 Supervisor 트리에서 관리되어야 할 경우가 된다.
6.1 Supervisor 모듈의 API
- supervisor:start_child()
- 슈퍼바이저의 ChildSpec 목록에 자식을 추가하고, 자식 프로세스를 시작함.
- supervisor:terminate_child()
- 자식 프로세스를 종료함. ChildSpec은 목록에 남아있음.
- supervisor:restart_child()
- ChildSpec을 이용해 자식 프로세스를 재시작함.
- supervisor:delete_child()
- ChildSpec을 제거함.
- supervisor:check_childspecs()
- start_child()를 사용하기 전에 사용.
- 작성한 자식 Spec이 문제없는지 Validation 함.
- supervisor:count_children()
- Supervisor가 관리하고 있는 자식 프로세스의 개수를 확인함.
- Supervisor가 관리하고 있는 ChildSpec 개수 확인.
- Supervisor가 관리하는 프로세스 중 Worker, Supervisor 개수 확인
- supervisor:which_children()
- Supervisor가 관리하는 모든 자녀 목록을 확인할 수 있음.
Supervisor Behaviour를 구현해서 동작하는 프로세스는 위 API를 이용해 상호작용할 수 있다.
6.2 Supervisor API를 이용한 동적인 자식 프로세스 추가
1> band_supervisor:start_link(lenient).
{ok,0.709.0>}
2> supervisor:which_children(band_supervisor).
[{keytar,<0.713.0>,worker,[musicians]},
{drum,<0.715.0>,worker,[musicians]},
{bass,<0.711.0>,worker,[musicians]},
{singer,<0.710.0>,worker,[musicians]}]
3> supervisor:terminate_child(band_supervisor, drum).
ok
4> supervisor:terminate_child(band_supervisor, singer).
ok
5> supervisor:restart_child(band_supervisor, singer).
{ok,<0.730.0>}
6> supervisor:count_children(band_supervisor).
[{specs,4},{active,3},{supervisors,0},{workers,4}]
7> supervisor:delete_child(band_supervisor, drum).
ok
8> supervisor:restart_child(band_supervisor, drum).
{error,not_found}
9> supervisor:count_children(band_supervisor).
[{specs,3},{active,3},{supervisors,0},{workers,3}]
- erlang 쉘에서 API를 직접해서 동적으로 프로세스를 Supervisor 관리자 목록에 추가하거나, 제거할 수 있다.
위 코드에서는 drum, singer 프로세스를 종료시킨 후, singer 프로세스만 Supervisor에 의해서 재시작하도록 했다.
6.3 simple_one_for_one을 사용한 동적 프로세스 추가
위에서 사용한 방법은 프로세스를 동적으로 재시작하고 종료할 수 있지만, 한 가지 문제점이 있다. erlang에서는 ChildSpec, Worker Process를 Supervisor 프로세스가 List 형태로 보관하고 있는데, 너무 많은 WokrerProcess가 존재하는 경우 프로세스를 찾아내는데 필요한 시간이 O(N)이 된다. 성능 저하가 발생할 수 있는 부분이다.
만약 이런 경우라면 simple_one_for_one을 사용해서 문제를 개선해 볼 수 있다.
- simple_one_for_one은 동일한 Childspec을 가짐.
- simple_one_for_one로 생성된 프로세스는 딕셔너리 형태로 저장됨.
- 따라서 O(1)로 찾을 수 있음.
- simple_one_for_one 전략은 Supervisor를 이용해 수동으로 자식 프로세스를 제거, 재시작 할 수 없음.
- 종료는 terminate_child()에 Pid를 넣어주면 가능해졌다! 그러나 제거, 재시작은 아직도 되지 않음.
simple_one_for_one은 단일 Childspec만 관리하기 때문에 메모리에서도 이점을 가져갈 수 있다.
6.3.1 simple_one_for_one을 이용하기 위한 ChildSpec 정의
start => {p_musician, start_link, [singer, good]},
기존에 명시적으로 재시작했던 Supervisor는 StartFunction의 {M,F,A}의 Args에 필요한 값을 모두 설정했었다. 이를 테면 위처럼 말이다. 이것은 supervisor:start_link로 Supervisor가 실행할 때, erlang:apply(M, F, A)를 실행해서 자식 프로세스를 생성해주기 때문이다.
start => {p_musicians, start_link, []},
그러나 simple_one_for_one 전략을 사용하고, start_child()를 이용해 동적으로 프로세스를 생성하려고 할 때는 Args를 []로 작성해야한다. 이것은 start_child()가 프로세스를 시작할 때 erlang:apply(M, F, A ++ Args)를 사용하기 때문이다. 여기서 A = []이 될 것이고, Args는 start_child(SupervisorId, Args)의 Args가 될 것이다.
init(jamband) ->
SupervisorFlags =
#{
strategy => simple_one_for_one,
intensity => 3,
period => 60
},
ChildSpecs =
[
#{
id => jam_musician,
start => {p_musician, start_link, []},
restart => temporary,
shutdown => 1000,
type => worker,
modules => [p_musician]
}
],
{ok, {SupervisorFlags, ChildSpecs}};
이제 simple_one_for_one 전략으로 Supervisor를 실행할 수 있도록 init() 함수에 jamband를 추가하고 위와 같이 코드를 작성한다.
% Supervisor 시작
1> p_band_supervisor:start_link(jamband).
% 동적 자식 프로세스 생성
2> supervisor:start_child(band_supervisor, [djembe, good]).
Musician Janet Tennelli, playing the djembe entered the room
{ok,<0.690.0>}
% 동적 자식 프로세스 생성
3> supervisor:start_child(band_supervisor, [drum, good]).
Musician Arnold Ramon, playing the drum entered the room
{ok,<0.696.0>}
% 동적 자식 프로세스 생성
4> supervisor:start_child(band_supervisor, [guitar, good]).
Musician Wanda Perlstein, playing the guitar entered the room
{ok,<0.698.0>}
% 자식 프로세스 제거 실패 → simple_one_for_one에서는 할 수 없음.
5> supervisor:terminate_child(band_supervisor, djembe).
{error,simple_one_for_one}
얼랭 쉘에서는 다음과 같이 작성해 볼 수 있다.
- simple_one_for_one 전략을 사용하는 Supervisor를 생성한다.
- Supervisor에게 동적으로 자식 프로세스 생성을 요청한다.
- Supervisor에게 자식 프로세스 제거를 요청한다.
- 이 때 제거는 실패되는데, simple_one_for_one의 경우 자식 프로세스를 manual로 제거, 종료, 재시작할 수 없도록 되어있기 때문이다.
simple_one_for_one인 경우에는 등록된 atom으로는 supervisor를 제거할 수 없다. Pid를 직접 입력해주는 경우, OTP Supervisor를 통해서 등록된 자식 프로세스의 종료가 가능해졌다.
5> musicians:stop(drum).
Arnold Ramon left the room (drum)
ok
알아둬야 할 부분
-module(supervisor).
-behaviour(gen_server).
...
start_link(SupName, Mod, Args) ->
gen_server:start_link(SupName, supervisor, {SupName, Mod, Args}, []).
OTP의 Supervisor 역시 OTP의 gen_server 프레임워크를 기반으로 동작하고 있다.
'프로그래밍 언어 > erlang' 카테고리의 다른 글
erlang 공부 : Event handlers (0) | 2023.12.26 |
---|---|
erlang 공부 : Building an Application With OTP (0) | 2023.12.17 |
erlang 공부 : What is OTP? (0) | 2023.12.13 |
erlang 공부 : 컴파일 하기 (0) | 2023.12.13 |
erlang 공부 : Designing a Concurrent Application (0) | 2023.12.13 |