erlang 공부 : The Count of Applications

    참고

     

     

    1. 이번 챕터에서 할 일

    이번 챕터에서는 다음 작업을 진행하려고 한다.

    • 이전에 구현했던 ppool을 이용해서 특정 작업을 진행함.
    • 특정 작업은 .erl 파일을 읽고, 각 정규표현식에 해당되는 수식이 몇 개 있는지 Count 하는 것임. 

    이렇게 구현할 어플리케이션의 이름은 erlcount라고 한다. 

    다음과 같은 구조로 갈 것이고, 각 모듈의 역할은 다음과 같다.

    • erlcount_counter : 프로세스 풀의 워커로 동작함. 
    • erlcount_sup : 워커 프로세스의 슈퍼바이저로 동작함. 
    • erlcount_dispatch : 디렉토리 탐색 / ppool에게 워커 스케쥴링 요청하는 책임을 가진 단일 서버. (erlcount_sup에 생성 요청)
    • erlcount_lib : 다른 모듈에서 호출할 수 있도록 API를 제공함.
    • erlcount : OTP application Callback용 모듈 (behaviour) 
    % erlcount-1.0/
    ebin/
     - erlcount.app
    include/
    priv/
    src/
     - erlcount.erl
     - erlcount_counter.erl
     - erlcount_dispatch.erl
     - erlcount_lib.erl
     - erlcount_sup.erl
    test/
    Emakefile

    이를 위한 모듈 구조는 다음과 같다.

     

    2. 코드 작성하기

    위의 모듈을 구현하는 코드를 작성하고자 한다.

     


    2.1 application 파일 작성

    {application, erlcount,
      [{vsn, "1.0.0"},
        {modules, [erlcount, erlcount_sup, erlcount_lib,
          erlcount_dispatch, erlcount_counter]},
        {applications, [ppool]},
        {registered, [erlcount]},
        {mod, {erlcount, []}},
        {env,
          [{directory, "."},
            {regex, ["if\\s.+->", "case\\s.+\\sof"]},
            {max_files, 10}]}
      ]}.

    Application 파일은 다음과 같이 작성할 수 있다.

    • {applications, [ppool]} → erlcount app이 시작하기 전에 실행되어야 할 어플리케이션 목록을 제공함. 이게 없는 경우, 에러 발생함. 
    • {registered, [erlcount]} → erlcount 프로세스는 사실 이름 없이 동작할 수 있음. 그러나 ppool App이 ppool_server로 제공받은 값을 저장하기 때문에 이렇게 설정함.
    • {env, {...}} → 환경변수. Application 내에서 실행 중인 모든 프로세스에서 접근 가능함. (메모리에 저장됨) 
      • application:get_env() 함수를 이용해서 접근 가능함. 
      • application:get_env(regex), application:get_env(directory), application:get_env(max_files) 등으로 정의된 값을 받아올 수 있음.

     


    2.2 erlcount Application 작성

    이번 어플리케이션은 위와 같은 구조로 작성하게 될 것이다. 이를 참고해서 erlcount 어플리케이션을 작성해야한다. 

    -module(erlcount).
    -author("ojt90").
    -behaviour(application).
    
    %% API
    -export([start/2, stop/1]).
    
    start(normal, _Args) ->
      erlcount_sup:start_link().
    
    stop(_State) ->
      ok.

    erlcount 어플리케이션은 erlcount_sup를 시작하는 것으로 어플리케이션을 시작한다. 

     


    2.3 erlcount_sup 모듈 작성

    -module(erlcount_sup).
    -author("ojt90").
    -behaviour(supervisor).
    
    %% API
    -export([start_link/0, init/1]).
    
    
    start_link() ->
      supervisor:start_link(?MODULE, []).
    
    init([]) ->
      MaxRestart = 5,
      MaxTime = 100,
      SupFlags =
        #{
          strategy => one_for_one,
          intensity => MaxRestart,
          period => MaxTime},
      ChildrenSpec =
        [
          #{
            id => dispatch,
            start => {erlcount_dispatch, start_link, []},
            restart => transient,
            shutdown => 60000,
            type => worker,
            modules => [erlcount_dispatch]
          }
        ],
        {ok, {SupFlags, ChildrenSpec}}.
    • erlcount_sup는 관리자 역할을 할 것이고, 관리의 대상은 erlcount_dispatch다. (디스패처는 특정 이벤트가 발생했을 때 적절한 함수를 호출하는 역할을 하는 것을 의미한다.)
    • 우선 이것을 고려해서 erlcount_sup의 init() 구현해본다.
      1. 각 디스패처는 다른 디스패처와 독립적으로 동작할 것이기 때문에 one_for_one 전략을 설정한다. 
      2. 자식으로 관리될 프로세스는 erlcount_dispatch 모듈이다.

    여기서 디스패처 모듈의 역할은 특정 디렉토리를 검색해서 '.erl' 파일이 있는지 확인하는 것이 된다.


    2.4 dispatcher 모듈은 어떻게 작성해야할까?

    앞서 이야기한 것처럼 디스패처 모듈은 ''디렉토리를 탐색하고, .erl 파일이 있으면 반환하는 역할'을 해야한다. 디스패처 모듈의 요구사항을 정리하면 다음과 같다.

    • 여러 정규식이 있더라도 디렉토리를 단 한번만 살펴봐야함. (같은 디렉토리를 2번씩 살펴보지 않아야 함)
    • .erl 파일을 발견하는 즉시 작업(Task)를 예약할 수 있어야 함. 즉, 전체 목록이 반환될 때까지 기다리지 않아도 됨.
    • 각 정규식별 카운트를 측정하기 위해, 정규식별로 카운터를 보관해야 함.
    • 디렉토리에서 .erl 파일을 다 찾기 전에, 이전 .erl 파일에 대한 Task 결과를 먼저 받을 수도 있음. 
    • erlcount_counters (Process Pool에 예약된 프로세스)에 병렬로 기동할 수 있음.
    • 디렉토리에서 파일 조회를 마쳐도, Task 결과가 계속 나올 수 있음. (파일이 많거나 복잡한 정규식인 경우)

    여기서 주로 고려해야 할 부분은 다음과 같다.

    1. 디렉토리를 재귀적으로 검색
    2. 결과를 가져와서 Task에 스케쥴링하고, Task 결과를 다시 혼란스럽지 않게 받을 수 있는 방법

    그런데 이렇게 작성하는 것은 쉽지가 않은데, 기존에 작성해둔 프로세스 풀(ppool) 모듈 아키텍쳐를 기반으로 하려고 하기 때문이다. 프로세스 풀의 워커 프로세스들은 '파일 내용을 정규표현식으로 Parsing'하는 작업을 하도록 해야하는데, 혀내 erlcount_dispatch 모듈은 그런 형태로 작성하기 어렵다. 이를 위해서 Continuation-Passing Style로 코드를 작성할 수 있다.

     

     

    2.4.1 Continuation Passing Style

    Contiunation Passing Style은 재귀성이 강한 하나의 함수를 가지고 모든 단계를 세분화하는 것이다. 핵심은 계속 작업 진행이 필요한 경우, '함수'를 반환해서 그 함수를 재귀적으로 실행하도록 하는 것이다. 여기서 erlcount_dispatch 모듈이 디렉토리를 탐색할 때, Continuation Passing Style을 적용하면 된다. erlcount_distpach 모듈에서 사용할 유틸리티성 모듈을 하나 만들고, 그곳에 Continuatino Passing이 가능하도록 코드를 작성한다. 

    -module(erlcount_lib).
    -author("ojt90").
    -include_lib("kernel/include/file.hrl").
    
    %% API
    -export([find_erl/1, regex_count/2]).
    
    find_erl(Directory) ->
      find_erl(Directory, queue:new()).
    
    find_erl(Name, Queue) ->
      {ok, F = #file_info{}} = file:read_file_info(Name),
      case F#file_info.type of
        directory -> handle_directory(Name, Queue);
        regular -> handle_regular_file(Name, Queue);
        _Other -> dequeue_and_run(Queue) % 다른 파일인 경우 무시함.
      end.
    
    
    handle_directory(Dir, Queue) ->
      case file:list_dir(Dir) of
        {ok, []} -> dequeue_and_run(Queue);
        {ok, Files} -> dequeue_and_run(enqueue_many(Dir, Files, Queue)) % 파일이 존재하면 해당 파일을 대기열에 넣음. 그리고 현재 값은 디큐함.
      end.
    
    handle_regular_file(Name, Queue) ->
      case filename:extension(Name) of
        ".erl" -> {continue, Name, fun() -> dequeue_and_run(Queue) end};
        _NonErl -> dequeue_and_run(Queue)
      end.
    
    
    dequeue_and_run(Queue) ->
      case queue:out(Queue) of
        {empty, _} -> done;
        {{value, File}, NewQueue} -> find_erl(File, NewQueue)
      end.
    
    
    enqueue_many(Path, Files, Queue) ->
      F = fun(File, Q) -> queue:in(filename:join(Path,File), Q) end,
      lists:foldl(F, Queue, Files).
    
    
    
    
    % Added after
    regex_count(Re, Str) ->
      case re:run(Str, Re, [global]) of
        nomatch -> 0;
        {match, List} -> length(List)
      end.
    1. find_erl()이 재귀성이 강한 함수가 된다. find_erl()에서 재귀를 하다가, 더 이상 찾을 것이 없으면 done을 반환한다. 만약 .erl을 찾으면 continue와 실행해야 할 함수를 반환하도록 작성한다.
      1. 디렉토리면 handle_directory(), 원하는 파일이면 regular, 그렇지 않은 경우에는 큐에서 제거한다.
    2. handle_directory()는 현재 디렉토리에 파일이 있으면 enqueue_many()를 호출해서 큐에 파일 경로를 집어넣은 다음 다음 작업을 진행해주는 형태가 된다. 이 때, dequeue_and_run()을 호출해서 다음 작업을 진행한다.
    3. dequeue_and_run()은 Queue에 있는 파일 경로를 하나 뺀다.
      1. 파일 경로가 있으면 find_erl()을 호출해서 재귀 작업을 한다.
      2. 없는 경우 done을 반환한다. (재귀 종료) 
    4. enqueue_many()는 큐에 해당 파일 경로를 추가한다.
    5. handle_regular_file() 에서는 '.erl' 파일인지를 확인한다.
      1. 만약 '.erl' 파일이라면, continue와 함께 호출해야 할 함수를 반환한다. 이 때, 함수 객체가 생성될 때 클로저에 의해서 생성된 함수 객체는 Queue를 참조하고 있게 된다. 여기가 재귀의 또 다른 끝이 된다.
      2. '.erl' 파일이 아닌 경우면, 큐에서 다음 파일 경로를 찾아서 동일한 작업을 진행한다.
      3. 여기가 CPS (Continuation Passing Style)이 완성되는 지점이 된다. 

     

     

    2.4.2 erlcount_dispatch의 요구사항 생각해보기.

    디렉토리 탐색 / 파일 정규표현식 작업 스케쥴링 / 작업 완료 수신받기 등을 동시에 잘 진행하려면 어떻게 해야할까? 저자는 gen_fms을 이용하는 것이 좋다고 한다. 

    먼저 상태는 다음과 같이 나눠볼 수 있다.

    • dispatching : find_erl() CPS 함수가 done을 반환하기를 기다리는 상태. find_erl()은 재귀적으로 디렉토리를 전부 탐색하고 있으니, 디렉토리를 탐색하는 상태로 볼 수 있음.
    • listening : 결과가 완료되기를 기다리는 상태. 

    전반적인 형태는 다음과 같이 구성할 수 있을 것이다

    1. Dispatching 상태에서는 CPS를 통해 get_files를 계속 호출한다.
    2. get_files 결과가 dispatching State에 전달될 때 마다, Ppool 모듈에 dispatching 한 후에 CPS를 실행한다. 
    3. ppool 모듈에 예약된 작업은 dispatching / listening 상태 모두에 반환될 수 있음. 
    4. ppool 모듈에서 워커의 작업이 완료될 때 마다 FSM에게 global 메세지를 전송함.
    5. get_files()에서 done이 반환되면 dispatching → listening으로 상태 전이됨. 

     

     

    2.4.3. erlcount_dispatch 코드 작성 

    -module(erlcount_dispatch).
    -behaviour(gen_fsm).
    -export([start_link/0, complete/4]).
    -export([init/1, dispatching/2, listening/2, handle_event/3,
    handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
     
    -define(POOL, erlcount).

    모듈의 API는 기본적으로 다음과 같이 구성된다.

    • supervisor에서 사용할 start_link/0, complete/4
    • State 함수 dispatching, listening. 나머지는 gen_fsm 콜백임. 
    • POOL 매크로는 ppool 모듈에 프로세스 풀을 등록할 이름을 의미함.

    앞서 설계에 대해서 이야기 했을 때, find_erl()에서 파일을 찾을 때 마다 비동기식으로 작업을 처리해야한다고 했다. (이 때 작업은 해당 파일에서 정규표현식에 매칭되는 녀석들을 찾는 것).

    위의 Flow Chart처럼 Dispatcher는 ppool workers에게 비동기로 작업 요청을 하고, ppool workers는 작업이 완료되면 Dispatcher에게 통지한다. 그러나 Dispatcher는 ppool:run_async/2를 이용해서 비동기 작업을 요청할 것이기 때문에 스케쥴링한 작업이 완료되었는지 알 수 있는 방법이 없다. (여러 메세지가 도착했을 때, 어떤 작업이 끝났는지 어떻게 구분할 수 있을까?)

    이 때 두 가지 방법을 이용해 볼 수 있다.

    • ppool:run_async/2로 요청할 때, Timeout을 전송하기.
    • ppool:run_async/2로 요청을 보낼 때, make_ref()를 이용해 고유한 참조와 함께 전송. 작업자는 작업이 완료되면 참조를 포함해서 응답함. 그러면 Dispathcer는 어떤 파일의 작업이 끝났는지 알 수 있음. 

    Timout을 포함해서 전송하는 경우, Timeout을 일일이 처리해야하기 때문에 코드가 복잡해 질 수 있다. 두번째 방법을 선택한 경우, Dispatcher가 받은 메세지가 어떤 작업의 결과인지를 Reference를 통해서 구별할 수 있기 때문에 위 문제가 해결된다.

    -record(data, {regex=[], refs=[]}).

    이런 이유 때문에 gen_fsm이 보관할 데이터는 다음과 같이 정의될 수 있다. refs에는 앞서 이야기한 Reference를 보관한다.

     

    2.4.4 erlcount_dispatch 추가코드 

    %% Public API
    start_link() ->
      gen_fsm:start_link(?MODULE, [], []).
    
    complete(Pid, Regex, Ref, Count) ->
      gen_fsm:send_all_state_event(Pid, {complete, Regex, Ref, Count}).
    1. start_link()는 이 모듈의 State 머신 프로세스를 기동할 때 사용한다. 
    2. complete()는 ppool_worker가 Task를 완료했을 때, State 머신에 응답할 때 사용하는 API다

    응답할 때, Worker는 Regex, Ref, Count만 반환한다. 여기서 Ref는 'Dispatcher가 보낸 것이 맞는지 파악하는 역할'을 한다. 또한 뒤에서 이야기 할 것이지만, 프로세스끼리 통신할 때는 필요한 메세지만 전송해서 네트워크를 통하는 메세지가 최소화 되어야 한다. 

    init([]) ->
      {ok, Re} = application:get_env(regex),
      {ok, Dir} = application:get_env(directory),
      {ok, MaxFiles} = application:get_env(max_files),
      pool_api:start_pool(?POOL, MaxFiles, {erlcount_counter, start_link, []}),
      case lists:all(fun valid_regex/1, Re) of
        true ->
          self() ! {start, Dir},
          {ok, dispatching, #data{regex=[{R,0} || R <- Re]}}; % 각 정규식에 카운터 추가함.
        false ->
          {stop, invalid_regex}
      end.
      
    valid_regex(Re) ->
      try re:run("", Re) of
        _ -> true
      catch
        error:badarg -> false
      end.

    gen_fsm 모듈을 start하면, init() 함수가 호출된다.

    1. 환경변수에서 Regex / Dir / MaxFiles(동시성) 값을 불러온다.
    2. gen_fsm 서버가 시작될 때, Worker용 프로세스 풀을 스타트한다. 
    3. 정규표현식이 정상적인지 validation 한다. (valid_regex)
      1. 만약 문제가 있다면, Fast Fail. 
      2. 그렇지 않다면, distpacthing State로 State Transition.
    4. Validation이 성공하면, 미리 자기 자신에게 {start, Dir}이라는 메세지를 보낸다. 그리고 이 메세지를 handle_info()가 읽어서 처리하도록 한다. 
    handle_info({start, Dir}, State, Data) ->
      gen_fsm:send_event(self(), erlcount_lib:find_erl(Dir)),
      {next_state, State, Data}.
    1. init() 메서드에서 자기 자신에게 보낸 메세지 {start, Dir}를 handle_info에서 읽는다.
    2. gen_fsm:send_event()를 호출해 자기 자신에게 CPS(Continuation Process Style)로 작성된 find_erl()을 호출해서 작업을 시작한다.

    {next_state, State, Data}를 통해서 상태를 그대로 유지한다. init() 호출 직후에는 Dispatching 상태가 되므로, Dispatching 상태를 유지한다. 

    dispatching({continue, File, Continuation}, Data = #data{regex=Re, refs=Refs}) ->
      F = fun({Regex, _Count}, NewRefs) ->
        Ref = make_ref(),
        pool_api:async_queue(?POOL, [self(), Ref, File, Regex]),
        [Ref|NewRefs]
          end,
      NewRefs = lists:foldl(F, Refs, Re),
      gen_fsm:send_event(self(), Continuation()),
      {next_state, dispatching, Data#data{refs = NewRefs}};
    dispatching(done, Data) ->
      listening(done, Data).
    1. dispatching() 메서드로 제어권이 넘어왔다는 것은 dispatching State 일 때, gen_fsm 프로세스에 어떤 메세지가 전송된 경우다. 이 때, {continue, ... }로 시작하는 메세지라면 첫번째 dispatching() 메서드로 패턴매칭된다. 
      1. ppool의 Worker Process에게 정규표현식을 Parsing하는 작업을 async_queue로 요청한다.
      2. lists:foldl()을 이용해 각 작업으로 인해 생성된 Refernece들을 Ref에 Accumulation하는 연산을 한다.
      3. 연산이 완료되면 다음 State로 넘어간다. 
    2. done이라는 메세지가 전송되는 경우, listeneing() 상태로 넘어간다. 
      1. done이라는 메세지는 find_erl()이 더 이상 파싱할 데이터가 없는 경우에 응답하는 값이다. 

    {continue, ...} / done 메세지는 모두 CPS로 작성된 find_erl()이 반환해주는 값이다. continue인 경우, '.erl'로 끝나는 파일을 찾은 것이고, Continuation은 다음에 진행할 작업을 정의한 함수다. find_erl()은 재귀 함수인데, 무한히 특정 작업만 '재귀'로 수행하지 않는다. 재귀 도중에 특정 조건이 만족되면, 재귀에서 잠시 벗어나 다음 작업을 할 수 있도록 Context 스위칭을 해주는 형태이고, 이것이 CPS 형식으로 구현된 것이다. 

    다음은 listening() 함수를 구현해야하는데, State Diagram을 바탕으로 이해해보자.

    1. 왼쪽은 Dispatch State → Listening State로 전환하고, 마지막 Result를 받는 경우를 고려해 볼 수 있다. (Dispatch는 파일을 탐색하고 있는 중, listening은 파일 탐색이 완료된 경우). 마지막 add_workers를 하고 얼마 지나지 않아 done을 받고 listening state로 transition을 할 것이다. listening 상태에서 Result를 받기 때문에 마지막 Result를 받은 후 '모든 결과를 다 받음'이라고 처리하기만 하면 됨.
    2. 오른쪽은 Dispatching에서 아직 find_erl()이 동작하고 있는 상태일 때다. 이 때, results는 계속 반환되고 있는데 listening State 전에 반환이 모두 되고, 그 후에 listening State가 되는 경우다. 이런 경우, listening State에서 종료를 해야하는데 어떠한 메세지도 받지 못했기 때문에 FSM은 끊임없이 메세지 받기를 기다릴 것이다. 
    listening(done, #data{regex=Re, refs=[]}) -> % all received!
    	[io:format("Regex ~s has ~p results~n", [R,C]) || {R, C} <- Re],
    	{stop, normal, done};
    listening(done, Data) -> % entries still missing
    	{next_state, listening, Data}.

    listening State에서는 refs가 존재하지 않을 때 State Machine을 종료해야한다. 즉, listening 상태에서 메세지가 하나 들어왔다고 종료하는 것이 아니라 refs = []이 될 때까지 재귀적으로 계속 들어줘야한다는 것이다. 따라서 위와 같이 구현할 수 있다. 

    Worker가 작업을 마치면 dispatching / listening State에 관계없이 메세지를 전송하는데, 이것은 global 메세지가 된다.

    % Worker에게서 종료 메세지를 수신받은 경우
    handle_event({complete, Regex, Ref, Count}, State, Data = #data{regex=Re, refs=Refs}) ->
      {Regex, OldCount} = lists:keyfind(Regex, 1, Re),
      NewRe = lists:keyreplace(Regex, 1, Re, {Regex, OldCount + Count}),
      NewData = Data#data{regex=NewRe, refs=Refs -- [Ref]},
      case State of
        dispatching -> {next_state, dispatching, NewData};
        listening -> listening(done, NewData)
      end.
    • fsm에서 global 메세지를 핸들링하는 callback인 handle_event()를 구현한다. 
      • 일반적인 FSM에서는 {next_state, ...}를 호출한다.
      • 그러나 여기서는 listening 상태일 때는 직접 listening(done, NewData)를 호출해야한다. 

    그렇게 해야하는 이유는 listening 상태에서 끝난 상태인지를 구별할 수 없기 때문이다. 현재 Worker의 Task가 끝나면 global Event로 메세지를 전송해주는데, 그 메세지는 handle_event()에서 처리된다. handle_event()에서 메세지를 처리하고 listening 상태로 넘겨줘야하는데, 이 때 두 가지 방법으로 State Transition하면서 '끝났는지를 판단'할 수 있다.

    1. 위처럼 listening(done, NewData)를 직접 호출해서 다음 상태로 패턴매칭 하는 경우.
    2. self() ! {done} 메세지를 보내고 {next_state, State, NewData}로 하는 경우 

    둘다 조삼모사이기 때문에 책에 나온대로 listening(done, NewData)를 직접 호출하기로 했다. 

    handle_sync_event(Event, _From, State, Data) ->
      io:format("Unexpected event: ~p~n", [Event]),
      {next_state, State, Data}.
     
    terminate(_Reason, _State, _Data) ->
      ok.
     
    code_change(_OldVsn, State, Data, _Extra) ->
      {ok, State, Data}.

    이후 더 필요한 콜백을 구현한다. 

     

    2.5. The Counter 구현

    Counter는 다음 작업을 하는 모듈이다. 

    1. 파일을 연다.
    2. 해당 파일에 정규표현식을 실행해서 해당되는 값을 찾는다.
    3. 결과를 반환한다.  → erlcount_dispatch:complete/4를 이용해서 디스패처에게 결과 전송.
    -module(erlcount_counter).
    -author("ojt90").
    -behaviour(gen_server).
    -record(state, {dispatcher, ref, file, re}).
    
    
    %% API
    -export([init/1, handle_call/3, handle_cast/2, start_link/4, handle_info/2, terminate/2, code_change/3]).
    
    start_link(DispatcherPid, Ref, FileName, Regex) ->
      gen_server:start_link(?MODULE, [DispatcherPid, Ref, FileName, Regex], []).
    
    
    init([DispatcherPid, Ref, FileName, Regex]) ->
      self() ! start,
      {ok, #state{
        dispatcher = DispatcherPid,
        ref = Ref,
        file = FileName,
        re = Regex}}.
    
    handle_call(_Msg ,_From, State) ->
      {noreply, State}.
    
    handle_cast(_Msg, State) ->
      {noreply, State}.
    
    handle_info(start, S = #state{re=Re, ref=Ref}) ->
      {ok, Bin} = file:read_file(S#state.file),
      Count = erlcount_lib:regex_count(Re, Bin),
      erlcount_dispatch:complete(S#state.dispatcher, Re, Ref, Count),
      {stop, normal, S}.
    
    terminate(_Reason, _State) ->
      ok.
    
    code_change(_OldVsn, State, _Extra) ->
      {ok, State}.

    결과적으로 다음과 같이 코드를 구현하면 된다.

    1. gen_server 프레임워크를 구현
    2. State에 Dispatcher PID를 가지고 있고, 작업이 완료되면 Dispatcher에게 응답함. 

     

     


    3. Run App Run

    home
    ├───ebin
    ├───erlcount-1.0
    ├───ppool-1.0

    먼저 만들어 둔 Application을 실행하기 위해서는 다음 디렉토리 구조를 만들어야 한다.

    $ erl -env ERL_LIBS "."

    위 구조를 만들고 home 디렉토리에서 다음 명령어를 작성한다. 여기서 ERL_LIBS라는 것은 특수한 환경변수다. 

    • ERL_LIBS는 얼랭이 OTP 어플리케이션을 찾을 수 있는 위치를 지정함. 
    • ERL_LIBS이 지정되면, VM은 자동으로 이 변수를 검색해 ebin/ 디렉토리를 검색함. 

     

    3.1 어플리케이션 실행해보기

    $ erl -env ERL_LIBS "."
    >>>
    
    1> application:load(ppool).
    ok
    
    2> application:start(ppool), application:start(erlcount).
    ok
    Regex if\s.+-> has 20 results
    Regex case\s.+\sof has 26 results
    1. application:load()는 어플리케이션 모듈을 메모리에서 찾을 수 있는 경우, 로드하려고 시도함. 
    2. application:start()를 이용해서 어플리케이션을 시작하고 결과를 받아볼 수 있따. 

     

    3.2 어플리케이션 실행 시, 새로운 환경변수 제공하기

    기본적으로 application 파일에 환경변수를 지정할 수 있다. 그러나 단순 환경변수 수정 때문에 얼랭 어플리케이션을 새로 빌드하는 것은 시간 낭비다. 새롭게 빌드하지 않아도 환경변수만 바꿀 수 있도록 기능을 지원해준다. 

    $ erl -env ERL_LIBS "." -erlcount directory '"/home/ferd/otp_src_R14B02/lib/"' regex '["[Ss]hit","[Dd]amn"]' max_files 50
    ...
    
    1> application:start(ppool), application:start(erlcount).
    ok
    Regex [Ss]hit has 13 results
    Regex [Dd]amn has 6 results
    
    2> q().
    ok

    위의 예시처럼 erl 뒤에 "-<어플리케이션 이름> <환경변수 이름> <원하는 값>"을 설정해주면, 어플리케이션이 시작될 때 application에 설정된 환경변수가 아니라 커맨드라인으로 입력된 값을 환경변수로 사용한다. 

     


    4. Included Applications

    Included Application은 위에서 했던 작업을 좀 더 손쉽게 하는 방법이다. 그러나 Included Application은 코드 재사용을 하기 어렵게 만든다는 점 때문에 사용하지 않는 것이 권장된다. 

    Included Application으로 사용하기 위해서는 다음 작업을 해야한다. 

    1. 한 어플리케이션을 다른 어플리케이션의 일부로 정의한다. (이 경우, 두 어플리케이션이 모두 수정되어야 함)
    2. 시작하는 코드를 몇 줄 추가해야함. 

     

     


    5. Complex Terminations

    Application을 종료할 때는 복잡한 작업이 필요할 수 있다. Graceful하게 종료해야하는 경우도 요구되기 때문이다. 

    • application:stop/1은 어플리케이션이 이미 종료된 후 호출됨. 

    이런 이유 때문에 application:stop/1은 Graceful한 종료에는 사용할 수 없다. 어플리케이션을 Graceful하게 종료하기 위해서는 application 콜백 모듈에 prep_stop(State) 함수를 추가하기만 하면 된다. 

    • 이 때 State는 application:start/2가 반환하는 State임. 
    • application:prep_stop/1의 반환하는 값이 application:stop/1의 입력값이 됨. 

    따라서 application:start → application:prep_stop → application:stop의 형태로 동작하게 되면, prep_stop은 아직 어플리케이션이 종료되기 전에 호출된다.

    '프로그래밍 언어 > erlang' 카테고리의 다른 글

    erlang : 코테 풀면서 문법 정리  (0) 2024.02.19
    얼랭 가드절의 패턴매칭  (0) 2024.01.21
    Erlang 공부 : Distribunomicon 2부  (0) 2024.01.07
    Erlang 공부 : Distribunomicon  (0) 2024.01.02
    Erlang : ETS Table  (0) 2023.12.31

    댓글

    Designed by JB FACTORY