참고
이전글
5. FSM을 위한 예제 → 게임 내의 거래 시스템
실용적인 예제를 위해 온라인 게임에서 플레이어들끼리 거래를 FSM으로 구현해보고자 한다. 아래 FSM은 복잡하기 때문에 시나리오 자체를 이해하는데 난이도가 높다.
5.1 시나리오 설정
- 거래 요청
- 거래 수락
- 아이템 제안
- 제안 철회
- 거래 준비 완료
- 일방적으로 거래 취소하기
게임 내의 거래 시스템은 다음 작업이 가능해야한다. 그리고 전반적인 동작 방식은 다음과 같다.
- 각 플레이어는 자신의 FSM을 가진다. → A는 A's FSM을 가짐.
- 각 플레이어는 FSM을 통해 통신한다. → A는 A's FSM에게 B에게 아이템으로 보내라 함. A's FSM은 B's FSM에게 이 이벤트를 전송한.
- 각 FSM이 작업을 수행하면 상대 FSM도 이것을 인지할 수 있어야 함.
5.2 동기식 메세지? 비동기식 메세지?
위 FSM을 구현하기 위해 각 프로세스는 동기식 메세지를 발송해야할까? 혹은 비동기식 메세지를 발송해야할까? 답은 비동기으로 메세지를 발송해야하는데, 그 이유는 동기식 메세지 전송 방식이 DeadLock을 초래할 수 있기 때문이다.
예를 들어 A → B로 메세지 전송, B → A 메세지 전송을 동시에 완료하고 동시에 특정한 패턴의 메세지를 기다리는 시나리오를 생각해보자. 이 경우 둘다 특정한 패턴의 메세지를 기다리기 때문에 Deadlock에 걸리게 된다.
이것을 해결하기 위한 방법은 두 가지가 있다.
- Receive에 Timeout을 설정한다.
- 비동기식 요청만 사용한다.
Timeout을 설정하면 Deadlock을 회피할 수는 있지만, 처리되지 못한 메세지가 메세지 박스에 남게 된다. 그리고 이는 Memory Leak를 초래할 수 있다. 따라서 이 비동기식 요청만을 이용하는 것이 더 효율적인 방법으로 이해할 수 있다.
5.3 큰 흐름
거래의 큰 흐름은 다음과 같다.
- 두 FSM 모두 Idle 상태일 때, Jim에게 거래 요청을 한다.
- Jim이 수락하면 거래가 진행된다.
- 거래 진행 시 아이템을 Offer / Cancel 할 수 있음.
- 두 사람이 모두 Ready 되었다고 선언한 경우 Trade가 성사된다.
아래에서는 각 케이스에 대한 State Transition을 살펴보려고 한다.
5.4 큰 관점의 State Transition
전반적인 State Transition은 다음과 같다. 아래에서 세분화 된 케이스를 살펴본다.
- 두 FSM이 모두 Idle일 때만 시작할 수 있음.
- 두 FSM이 모두 idle일 때 할 수 있는 유일한 작업은 다른 FSM에게 거래를 요청하는 것이다.
- 상대 FSM에게 요청을 보낸 FSM은 응답을 기다리며 idle_wait가 된다.
- Idle_wait 상태에서는 상대 FSM의 응답을 기다림.
- 상대 FSM이 idle_wait 상태에게 Accept를 응답하면 negotiate 상태로 Transation됨.
- 상대 FSM도 위 FSM의 StateTransition을 그대로 할 수 있어야 함.
- 따라서 위 FSM State diagram이 생성됨.
- 두 개의 State Diagram을 붙여보면 다음과 같은 그림이 됨.
- 동시성 문제를 고려해보면 다음과 같음.
- 두 FSM이 동시에 ask negotiate 메세지를 보내는 경우, 동시에 두 FSM이 idle_wait 상태가 된다.
- 한 FSM이 idle_wait 상태인데 ask negotiate 메세지를 수신한다는 것은 두 FSM이 Race Condition을 겪고 있는 상태로 해석할 수 있다.
- 이 Race Condition에서 두 FSM 모두 서로 거래를 원하는 상태이기 때문에 idle_wait 상태에 ask negotiate 메세지를 받더라도 negotiate State로 Transition 하는데 문제가 없다.
- 그러나 이 동시성 문제는 FSM이 단 2대만 있을 때만 정상적으로 동작한다. A → B / B → A / C → A인 경우에는 A는 B에 거래 요청을 했으나 오히려 C와 거래를 시작할 수도 있게 된다.
- Negotiate State에서는 상대 FSM에게 아이템을 제안(Offer)하고 철회(retract) 할 수 있음.
- 상대방 역시 나에게 offer & retract가 가능하다.
- 거래할 준비가 된 경우, 상대 FSM에게 ready 메세지를 보낼 수 있음.
- Ready 메세지를 보낸 후, 응답을 기다려야 하기 때문에 Wait State로 Transition함.
- Ready 메세지를 보낸 나의 FSM은 Jim의 FSM에게서 응답을 기다림.
- Jim FSM이 보내는 응답은 JIM FSM의 State에 따라 다름.
- JIM FSM이 wait 상태인 경우 → Ready 응답함.
- JIM FSM이 negotiate 상태인 경우 → Not Yet 응답함.
- 우리가 Ready 메세지를 보낸 후, Wait State로 Transition 한다.
- 그러나 JIM's FSM은 negotiate State이기 때문에 not_yet 응답을 한다. 이 때, wait 상태를 유지해야한다.
- Wait 상태인데 상대가 Offer(제안) / retract(철회)를 했을 때, negotiate 상태로 바뀌어야 한다.
- 그렇지 않으면 제시한 아이템을 다 빼버린 후, Ready를 눌러도 거래가 성사되기 때문이다.
- JIM도 ready를 누르면, wait 상태인 나의 FSM에게 ready? 메세지를 보낼 것이다. 이 때, JIM's FSM에게는 ready를 응답함.
- FSM이 하는 일은 Ready Reponse를 하지만, 정작 이 FSM는 wait 상태를 유지한다.
- 왜냐하면 잠재적인 Race Condition이 존재하기 때문이다. 동시성 문제는 다음과 같다.
- 우리 FSM이 negotiate 상태에서 Jim의 FSM에게 are_you_ready 메세지를 보내고 Wait 상태로 전환한다.
- Jim의 FSM이 Offer를 우리 FSM에게 보낸다. 우리 FSM은 메세지를 수신하고 negotiate → wait로 State Transition 한다.
- Jim의 FSM이 are_you_ready를 우리 FSM에게 보내고 Wait State로 Transition한다.
- 1번에서 보냈던 are_you_ready 메세지를 Jim FSM이 수신한다. Jim FSM은 wait 상태이므로 Ready State로 Transition한다.
- 3번에서 Jim의 FSM이 보낸 are_you_ready 메세지를 우리 FSM이 수신한다. negotiate 상태이므로 not yet을 Jim FSM에게 응답한다.
위와 같은 Race Condition 상태에서는 비동기로 처리 되지만, 특정 메세지 수신을 기다리며 사실상 Blocking 되는 경우가 발생할 수 있다. 이것을 해결하는 한 가지 방법은 간접 Layer를 하나 더 추가하는 것이다.
- Wait 상태에서 ready! 메세지를 받으면, ready!를 응답함 → 이중 Race Condition을 해결하기 위함이다.
- 이후 Ack 메세지를 상대방에게 전송한 후 Ready State로 transition한다. (상대방도 동일하게 Ack 메세지를 보낸 후 Ready State로 전환한다)
위 계층을 하나 추가하면서 (Are you ready + Ready!), 이전에 발생했던 Double Race Condition이 해결된 것을 볼 수 있다.
최종 거래는 두 FSM이 모두 Ready 상태에서 거래는 2 Phase Commit을 통해 이루어진다. 이 외에도 거래 취소를 통보하는 글로벌 이벤트도 FSM에 추가될 것이다.
6. 코드로 구현해보기 (거래 시스템)
위에서 정의한 FSM을 코드로 구현해본다.
6.1 모듈 선언
-module(trade_fsm).
-author("ojt90").
-behaviour(gen_fsm).
%% API
-export([start/1, start_link/1, trade/2, accept_trade/1,
make_offer/2, retract_offer/2, ready/1, cancel/1]).
%% gen_fsm callbacks
-export([init/1, handle_event/3, handle_sync_event/4, handle_info/3,
terminate/3, code_change/4,
% custom state names
idle/2, idle/3, idle_wait/2, idle_wait/3, negotiate/2,
negotiate/3, wait/2, ready/2, ready/3]).
먼저 위 코드처럼 모듈을 선언한다.
- gen_fsm callback으로 사용되는 함수
- FSM - FSM끼리 통신할 때 사용하는 함수
- Client → FSM으로 통신할 때 사용하는 함수
위 함수들이 해당 모듈에 포함되어야 한다.
6.2 Public API 구현
%% Public API
start(Name) ->
gen_fsm:start(?MODULE, [Name], []).
start_link(Name) ->
gen_fsm:start_link(?MODULE, [Name], []).
% 거래 요청
trade(OwnPid, OtherPid) ->
gen_fsm:sync_send_event(OwnPid, {negotiate, OtherPid}, 30000).
% 거래 수락
accept_trade(OwnPid) ->
gen_fsm:sync_send_event(OwnPid, accept_negotiate).
% 아이템 제시
make_offer(OwnPid, Item) ->
gen_fsm:send_event(OwnPid, {make_offer, Item}).
% 아이템 회수
retract_offer(OwnPid, Item) ->
gen_fsm:send_event(OwnPid, {retract_offer, Item}).
% 거래 준비 완료
ready(OwnPid) ->
gen_fsm:sync_send_event(OwnPid, ready, infinity).
% 거래 취소
cancel(OwnPid) ->
gen_fsm:sync_send_all_state_event(OwnPid, cancel).
위 코드로 Public API를 구현한다.
- 이 API는 Client가 본인의 FSM에게 요청을 보낼 때 사용하는 API다.
- sync_send_event(), send_event()를 각각 사용한다. 즉, 동기식 / 비동기식으로 각각 요청한다.
6.3 FSM - FSM 커뮤니케이션 함수
ask_negotiate(OtherPid, OwnPid) ->
gen_fsm:send_event(OtherPid, {ask_negotiate, OwnPid}).
accept_negotiate(OtherPid, OwnPid) ->
gen_fsm:send_event(OtherPid, {accept_negotiate, OwnPid}).
- 클라이언트에게 거래 요청을 하라고 FSM이 받으면, FSM은 다른 FSM에게 ask_negotiate()를 이용해서 거래 시작을 요청한다.
- accept_negotiate()는 ask_negotiate()에게서 메세지를 받은 FSM이 응답하는데 사용한다.
% 클라이언트 오퍼 전달
do_offer(OtherPid, Item) ->
gen_fsm:send_event(OtherPid, {do_offer, Item}).
% 클라이언트 오퍼 취소
undo_offer(OtherPid, Item) ->
gen_fsm:send_event(OtherPid, {undo_offer, Item}).
- 클라이언트가 아이템 오퍼를 FSM에게 요청하면, FSM은 do_offer()를 이용해 상대 FSM에게 요청을 보낸다.
- undo_offer()는 오퍼를 취소할 때 사용한다.
are_you_ready(OtherPid) ->
gen_fsm:send_event(OtherPid, are_you_ready).
not_yet(OtherPid) ->
gen_fsm:send_event(OtherPid, not_yet).
am_ready(OtherPid) ->
gen_fsm:send_event(OtherPid, 'ready!').
- 이 함수는 FSM끼리 Wait - Ready State에서 사용하는 함수다.
ack_trans(OtherPid) ->
gen_fsm:send_event(OtherPid, ack).
ask_commit(OtherPid) ->
gen_fsm:sync_send_event(OtherPid, ask_commit).
do_commit(OtherPid) ->
gen_fsm:sync_send_event(OtherPid, do_commit).
- 이 함수는 FSM이 Ready 상태에서 2 Phase Commit을 할 때 사용하는 함수다.
notify_cancel(OtherPid) ->
gen_fsm:send_all_state_event(OtherPid, cancel).
- 이 함수는 Global Event인 cancel을 받았을 때 사용하기 위한 함수다.
6.4 State Record 선언
FSM도 State를 가지고 메인 루프를 돌며 꼬리 재귀를 한다. 이 떄 사용할 State Record를 선언해야한다.
-record(state, {name="", other, ownItems=[], otherItems=[], monitor, from}).
이렇게 선언하고, 각각의 의미는 다음과 같다.
- other : 상대 FSM Pid
- ownItems : 내 FSM이 제안한 Item.
- otherItems : 상대 FSM이 제안한 Item
- monitor: 상대 FSM에 대한 Monitor
- from : FSM의 Client(Jim 같은 것들)
6.5 초기화 함수 선언
init(Name) ->
{ok, idle, #state{name = Name}}.
gen_fsm의 초기화 함수 init/1은 다음과 같이 선언한다. 여기서 주목해야 할 부분은 초기 State는 idle이라는 점이다.
6.6 유틸성 함수 구현
% Utility Function.
notice(#state{name=N}, Str, Args) ->
io:format("~s: " ++Str++ "~n", [N|Args]).
unexpected(Msg, State) ->
io:format("~p received unknown event ~p while in state ~p~n", [self(), Msg, State]).
add(Item, Items) ->
[Item | Items].
remove(Item, Items) ->
Items -- [Item].
priority(OwnPid, OtherPid) ->
OwnPid > OtherPid.
commit(S=#state{name=Name, ownItems = OwnItems, otherItems = OtherItems}) ->
io:format(
"Transaction completed for ~s."
"Items sent are:~n~p, ~n received are:~n~p.~n"
"This operation should have some atomic save "
"In a database. ~n",
[Name, OwnItems, OtherItems]).
유틸리티성 함수를 다음과 같이 구현한다.
- notice : io:format을 포팅한 함수.
- unexpected : 원치않는 메세지를 받았을 때 출력하는 용도.
- add : Item offer를 받았을 때 사용함.
- remove: undo offer를 받았을 때 사용함.
- priority : 2 Phase Commit 시, Race Condition에 의한 DeadLock을 처리하기 위해 Pid끼리 대소 비교해주는 함수.
- commit : 2 Phase Commit에서 사용함.
6.7 State Function 구현
여기서부터는 앞의 FSM State Transition에 정의해둔 State Function을 구현한다.
idle({ask_negotiate, OtherPid}, S=#state{}) ->
Ref = erlang:monitor(process, OtherPid),
notice(S, "~p asked for a trade negotiation", [OtherPid]),
{next_state, idle_wait, S#state{other = OtherPid, monitor = Ref}};
idle(Event, Data) ->
unexpected(Event, idle),
{next_state, idle, Data}.
- idle/2를 먼저 구현한다. 비동기식 요청에 대응되는 State Function이다.
- 상대 FSM에 대한 모니터를 생성하는데, 상대 FSM이 죽은 경우 메세지를 전송받아서 FSM에서 시작한 거래를 종료하기 위함이다.
% Client가 자신의 FSM에게 요청했을 때 처리하는 코드.
idle({negotiate, OtherPid}, From, S=#state{}) ->
ask_negotiate(OtherPid, self()),
notice(S, "asking user ~p for a trade", [OtherPid]),
Ref = monitor(process, OtherPid),
{next_state, idle_wait, S#state{other = OtherPid, monitor = Ref, from = From}};
idle(Event, _From, Data) ->
unexpected(Event, idle),
{next_state, idle, Data}.
- idle/3을 구현한다.
- 클라이언트가 다른 플레이어에게 거래 요청을 하는 경우, 동기식으로 요청하도록 코드가 작성되어있다.
- 클라이언트에게 요청을 받은 FSM은 ask_negotiate()로 상대 FSM에게 거래 요청을 하고, idle_wait 상태로 변환한다.
% should solve race condition.
idle_wait({ask_negotiate, OtherPid}, S=#state{other = OtherPid}) ->
% 나도 상대방에게 응답을 기다리고 있는 상태인데 이걸 받음. Race Condition임.
% 클라이언트에게 응답. (상대 FSM이 아님)
gen_fsm:reply(S#state.from, ok),
notice(S, "starting negotiation", []),
{next_state, negotiate, S};
idle_wait({accept_negotiate, OtherPid}, S=#state{other=OtherPid}) ->
% 클라이언트에게 응답. (상대 FSM이 아님)
gen_fsm:reply(S#state.from, ok),
notice(S, "starting negotiation", []),
{next_state, negotiate, S};
idle_wait(Event, Data) ->
unexpected(Event, idle_wait),
{next_state, idle_wait, Data}.
- idle_wait/2는 FSM끼리의 통신에서 주로 사용하는 콜백이다.
- idle_wait 상태에서는 Race Condition이 발생할 수 있고, 앞서 State Diagram에서 처리하는 방법에 대해 정리했다.
- 거래가 시작되는 경우, gen_fsm:reply()를 이용해서 클라이언트에게 결과를 전송한다.
idle_wait(accept_negotiate, _From, S=#state{other=OtherPid}) ->
accept_negotiate(OtherPid, self()),
notice(S, "accepting negotiation", []),
{reply, ok, negotiate, S};
idle_wait(Event, _From, Data) ->
unexpected(Event, idle_wait),
{next_state, idle_wait, Data}.
- idle_wait/3은 클라이언트가 보낸 요청을 처리하는 경우다.
- accept_negotiate()로 상대 FSM에게 협상을 받아들이는 것을 알리고, 클라이언트에게도 {reply, ok, ...}를 이용해 응답한다. 이 때, negotiate State로 Transition한다.
negotiate({make_offer, Item}, S=#state{ownItems=OwnItems}) ->
do_offer(S#state.other, Item),
notice(S, "offering ~p", [Item]),
{next_state, negotiate, S#state{ownItems=add(Item, OwnItems)}};
negotiate({retract_offer, Item}, S=#state{ownItems=OwnItems}) ->
undo_offer(S#state.other, Item),
notice(S, "cancelling offer on ~p", [Item]),
{next_state, negotiate, S#state{ownItems=remove(Item, OwnItems)}};
negotiate({do_offer, Item}, S=#state{ownItems=OwnItems}) ->
notice(S, "other player offering ~p", [Item]),
{next_State, negotiate, S#state{ownItems=add(Item, OwnItems)}};
negotiate({undo_offer, Item}, S=#state{ownItems=OwnItems}) ->
notice(S, "other player cancelling offer on ~p", [Item]),
{next_state, negotiate, S#state{ownItems=remove(Item, OwnItems)}};
negotiate(are_you_ready, S=#state{other=OtherPid}) ->
io:format("Other user ready to tread.~n"),
notice(S, "Other user ready to transfer goods:~n You get ~p, The other side gets ~p", [S#state.otherItems, S#state.ownItems]),
not_yet(OtherPid),
{next_state, negotiate, S};
negotiate(Event, Data) ->
unexpected(Event, negotiate),
{next_state, negotiate, Data}.
- negotitate/2를 구현한다. (FSM끼리 통신 + 클라이언트의 요청)
- make_offer / do_offer / retract_offer /undo_offer의 경우 negotiate 상태로 전환되고, 그에 맞게 Item이 업데이트 된다.
- negotiate state에서 are_you_ready를 받은 경우 준비되지 않았기 때문에 not_yet을 응답한다.
negotiate(ready, From, S = #state{other = OtherPid}) ->
are_you_ready(OtherPid),
notice(S, "asking if ready, waiting", []),
{next_state, wait, S#state{from=From}};
negotiate(Event, _From, S) ->
unexpected(Event, negotiate),
{next_state, negotiate, S}.
- negotitate/3을 구현한다. (클라이언트의 통신에서 사용함)
- 클라이언트가 ready 요청을 FSM에게 보내면, FSM은 상대 FSM에게 are_you_ready 메세지를 보내고, 본인은 Wait 상태로 전환한다. 그리고 클라이언트에게 응답한다.
wait({do_offer, Item}, S=#state{otherItems=OtherItems}) ->
gen_fsm:reply(S#state.from, offer_changed),
notice(S, "other side offering ~p", [Item]),
{next_state, negotiate, S#state{otherItems=add(Item, OtherItems)}};
wait({undo_offer, Item}, S=#state{otherItems=OtherItems}) ->
gen_fsm:reply(S#state.from, offer_changed),
notice(S, "other side cancelling offer on ~p", [Item]),
{next_state, negotiate, S#state{otherItems=remove(Item, OtherItems)}};
- do_offer / undo_offer를 받은 경우 negotitate State로 Transition 한다.
wait(are_you_ready, S=#state{other = OtherPid}) ->
am_ready(OtherPid),
notice(S, "asked if ready, and I am. Waiting for same reply", []),
{next_state, wait, S};
wait(not_yet, S=#state{}) ->
notice(S, "Other not ready yet", []),
{next_state, wait, S};
- are_you_ready를 받은 경우, 'ready!' 메세지를 상대에게 보내면서 wait state를 유지한다.
- not_yet 메세지를 받은 경우, 따로 할 응답은 없다. wait state를 유지한다.
wait('ready!', S=#state{other = OtherPid, from = From}) ->
am_ready(OtherPid),
ack_trans(OtherPid),
gen_fsm:reply(From, ok),
notice(S, "other side is ready. Moving to ready state", []),
{next_state, ready, S};
- 'ready!'를 받은 경우 상대방에게도 다시 한번 'ready!'를 보내준다. 이것은 Double Race Condition을 피하기 위함이다
- ack_trans()를 호출해서 2 Phase Commit을 시작하는 메세지를 상대에게 보낸다.
- 클라이언트에게 FSM이 Ready 상태로 변환되는 것을 알리고, Ready State로 Transition 한다.
ready(ack, S=#state{}) ->
case priority(self(), S#state.other) of
true ->
try
notice(S, "asking for commit", []),
ready_commit = ask_commit(S#state.other),
notice(S, "ordering commit", []),
ok = do_commit(S#state.other),
notice(S, "committing...", []),
commit(S),
{stop, normal, S}
catch Class:Reason ->
notice(S, "commit failed", []),
{stop, {Class, Reason}, S}
end;
false ->
{next_state, ready, S}
end;
ready(Event, Data) ->
unexpected(Event, ready),
{next_state, ready, Data}.
- Ready 상태가 되면 다른 메세지는 전혀 신경 쓰지 않기 때문에 좀 더 쉽게 프로토콜을 구현할 수 있다.
- Ready 상태가 되면 클라이언트가 요청하지 않아도 자동으로 2 Phase Commit으로 처리하고 싶다. 이 때, wait State Function에서 호출한 ack_trans() 메서드가 전송한 메세지로 2 Phase Commit이 실행된다.
- 문제는 두 FSM 모두 ack_trans() 메세지를 받았고, 위에서 실행하는 ready_commit() 같은 동기 메서드를 동시에 실행할 수 있기 때문에 DeadLock에 빠질 수 있다. 이것을 해결하기 위해 priority() 함수를 추가했다.
- priority() 함수는 두 FSM 프로세스의 PID를 비교해서, 우선순위가 빠른 녀석이 2 Phase Commit을 시작하고, 나머지는 2 Phase Commit 요청을 받도록 하기 위함이다
- true 인 경우 2 Phase Commit을 주도적으로 실행한다
- false 인 경우 2 Phase Commit 요청을 기다린다.
- 여기서 ok = do_commit() 같은 코드들은 패턴 매칭을 기대하는 것이다.
ready(ask_commit, From, S) ->
notice(S, "replying to ask_commit", []),
{reply, ready_commit, ready, S};
ready(do_commit, From, S) ->
notice(S, "comitting...", []),
commit(S),
{stop, normal, ok, S};
ready(Event, _From, Data) ->
unexpected(Event, ready),
{next_state, ready, Data}.
- Ready State Function에서 2 Phase Commit을 처리하기 위한 함수를 추가한다.
- 이전 Try ~ Cath 절에서 ask_commit / do_commit 이벤트를 전송하는데, 이런 이벤트를 받은 프로세스가 각각 어떻게 처리하는지를 정의한다.
- ask_commit을 받은 경우, ready_commit을 응답함.
- do_commit을 받은 경우, ok를 응답함.
- 이 응답은 이전 함수에서 작성한 패턴 매칭에서 사용된다. ok = do_commit()
6.9 Handle_event / handle_sync_event 나머지 콜백 구현
handle_event(cancel, _StateName, S=#state{}) ->
notice(S, "received cancel event", []),
{stop, other_cancelled, S};
handle_event(Event, StateName, Data) ->
unexpected(Event, StateName),
{next_state, StateName, Data}.
- 거래 도중 클라이언트가 Global Event로 거래 취소를 보낼 수 있다. 이 때, 비동기로 오는 요청을 처리하는 코드다.
- StateName이 넘어오는데, 모르는 Event가 전송된 경우 기존 State Function을 실행하도록 메인 루프를 작성할 수 있다.
handle_sync_event(cancel, _From, _StateName, S=#state{}) ->
notify_cancel(S#state.other),
notice(S, "cancelling trade, sending cancel event", []),
{stop, cancelled, ok, S};
handle_sync_event(Event, _From, StateName, Data) ->
unexpected(Event, StateName),
{next_state, StateName, Data}.
- 거래 도중 클라이언트가 Global Event로 거래 취소를 보낼 수 있다. 동기 요청을 처리하는 코드다.
- StateName이 넘어오는데, 모르는 Event가 전송된 경우 기존 State Function을 실행하도록 메인 루프를 작성할 수 있다.
handle_info({'DOWN', Ref, process, Pid, Reason}, _, S=#state{other=Pid, monitor=Ref}) ->
notice(S, "Other side dead", []),
{stop, {other_down, Reason}, S};
handle_info(Info, StateName, Data) ->
unexpected(Info, StateName),
{next_state, StateName, Data}.
- FSM끼리는 모니터를 이용해서 서로를 감시하고 있다.
- 상대 FSM 프로세스가 죽은 경우에는 더 이상 거래를 할 필요가 없어지기 때문에 'DOWN' 메세지를 받는 경우 FSM 자체를 종료한다.
code_change(_OldVsn, StateName, Data, _Extra) ->
{ok, StateName, Data}.
terminate(normal, ready, S=#state{}) ->
notice(S, "FSM leaving.", []);
terminate(_Reason, _StateName, _StateData) ->
ok.
나머지는 일반적인 콜백이다.
요약
- 동기식 요청은 분산 환경에서 DeadLock을 가져올 수 있다.
- 동기식 요청에서 발생할 수 있는 DeadLock을 해결하기 위해 비동기식 요청을 사용할 수 있다. 그러나 이 역시 Race Condition을 불러온다.
- Race Condition을 해결하기 위해서는 간접 Layer를 추가하는 것도 나쁘지 않다.
'프로그래밍 언어 > erlang' 카테고리의 다른 글
erlang 공부 : Building OTP Applications (1) | 2023.12.29 |
---|---|
인텔리제이 erlang 디버그 모드 설정 (0) | 2023.12.29 |
erlang 공부 : Rage Against The Finite-State Machines 1부 (0) | 2023.12.27 |
erlang 공부 : Event handlers (0) | 2023.12.26 |
erlang 공부 : Building an Application With OTP (0) | 2023.12.17 |