erlang 공부 : Rage Against The Finite-State Machines 2부

    참고

     


    이전글

     


    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에 걸리게 된다. 

    이것을 해결하기 위한 방법은 두 가지가 있다.

    1. Receive에 Timeout을 설정한다.
    2. 비동기식 요청만 사용한다. 

    Timeout을 설정하면 Deadlock을 회피할 수는 있지만, 처리되지 못한 메세지가 메세지 박스에 남게 된다. 그리고 이는 Memory Leak를 초래할 수 있다. 따라서 이 비동기식 요청만을 이용하는 것이 더 효율적인 방법으로 이해할 수 있다. 

     

    5.3 큰 흐름

    거래의 큰 흐름은 다음과 같다.

    1. 두 FSM 모두 Idle 상태일 때, Jim에게 거래 요청을 한다.
    2. Jim이 수락하면 거래가 진행된다.
    3. 거래 진행 시 아이템을 Offer / Cancel 할 수 있음.
    4. 두 사람이 모두 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이 존재하기 때문이다. 동시성 문제는 다음과 같다.
      1. 우리 FSM이 negotiate 상태에서 Jim의 FSM에게 are_you_ready 메세지를 보내고 Wait 상태로 전환한다.
      2. Jim의 FSM이 Offer를 우리 FSM에게 보낸다. 우리 FSM은 메세지를 수신하고 negotiate → wait로 State Transition 한다.
      3. Jim의 FSM이 are_you_ready를 우리 FSM에게 보내고 Wait State로 Transition한다.
      4. 1번에서 보냈던 are_you_ready 메세지를 Jim FSM이 수신한다. Jim FSM은 wait 상태이므로 Ready State로 Transition한다.
      5. 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를 추가하는 것도 나쁘지 않다. 

    댓글

    Designed by JB FACTORY