LoginSignup
10
10

More than 5 years have passed since last update.

すごいErlangゆかいに学んだメモ 第14章 OTPの紹介

Last updated at Posted at 2016-07-26

OTPというか、gen_server の概要を掴むためのメモ

汎用サーバーモジュールを作る

  • サーバー共通部分
-module(my_server).
-export([start/2, start_link/2, call/2, cast/2, reply/2]).

%%% 公開API
start(Module, InitialState) ->
    spawn(fun() -> init(Module, InitialState) end).

start_link(Module, InitialState) ->
    spawn_link(fun() -> init(Module, InitialState) end).

call(Pid, Msg) ->
    Ref = erlang:monitor(process, Pid),
    Pid ! {sync, self(), Ref, Msg},
    receive
        {Ref, Reply} ->
            erlang:demonitor(Ref, [flush]),
            Reply;
        {'DOWN', Ref, process, Pid, Reason} ->
            erlang:error(Reason)
    after 5000 ->
        erlang:error(timeout)
    end.

cast(Pid, Msg) ->
    Pid ! {async, Msg},
    ok.

reply({Pid, Ref}, Reply) ->
    Pid ! {Ref, Reply}.

%%% プライベート関数
init(Module, InitialState) ->
    loop(Module, Module:init(InitialState)).

loop(Module, State) ->
    receive
        {async, Msg} ->
             loop(Module, Module:handle_cast(Msg, State));
        {sync, Pid, Ref, Msg} ->
             loop(Module, Module:handle_call(Msg, {Pid, Ref}, State))
    end.
  • アプリケーションコード
-module(kitty_server2).

-export([start_link/0, order_cat/4, return_cat/2, close_shop/1]).
-export([init/1, handle_call/3, handle_cast/2]).

-record(cat, {name, color=green, description}).

%%% クライアントAPI
start_link() -> my_server:start_link(?MODULE, []).

%% 同期呼び出し
order_cat(Pid, Name, Color, Description) ->
    my_server:call(Pid, {order, Name, Color, Description}).

%% 非同期呼び出し
return_cat(Pid, Cat = #cat{}) ->
    my_server:cast(Pid, {return, Cat}).

%% 同期呼び出し
close_shop(Pid) ->
    my_server:call(Pid, terminate).

%%% サーバー関数
init([]) -> []. %% ここでは何もしない

handle_call({order, Name, Color, Description}, From, Cats) ->
    if Cats =:= [] ->
        my_server:reply(From, make_cat(Name, Color, Description)),
        Cats;
       Cats =/= [] ->
        my_server:reply(From, hd(Cats)),
        tl(Cats)
    end;

handle_call(terminate, From, Cats) ->
    my_server:reply(From, ok),
    terminate(Cats).

handle_cast({return, Cat = #cat{}}, Cats) ->
    [Cat|Cats].

%%% プライベート関数
make_cat(Name, Col, Desc) ->
    #cat{name=Name, color=Col, description=Desc}.

terminate(Cats) ->
    [io:format("~p was set free.~n",[C#cat.name]) || C <- Cats],
    exit(normal).

my_server, kitty_server2 の概要

  • my_server.erl には軽量プロセス化されるメインループ(loop関数)がある
    • my_server:call, my_server:cast はこのメインループのPidに対してsendする
    • メインループは渡された引数を元にサーバー関数である handle_cast, handle_call を呼び出す
  • kitty_server2 はサーバーの機能を呼び出すための公開APIと、実際に処理されるhandle_cast, handle_call が定義されている
    • kitty_server2自体はメインループを持たない
      • メインループやサーバーの状態保存は my_serverモジュールが持つ
    • kitty_server2 はサーバーに実際に提供したい機能のみを定義すれば良い
      • サーバーとして機能は my_serverモジュールが持つ
  • この my_server モジュールがOTPの gen_server の概要的モデルとなる

動作イメージ

  • my_server, kitty_server2 は大体以下のような関係、役割分担となる。

my_server_and_kitty_server2.png


gen_server ビヘイビアのコールバック関数

init/1

サーバー初期化関数

  • 返り値
    • {ok, State}
      • State がサーバーが保持する状態として、プロセスのメインループへ渡される。
    • {ok, State, TimeOut}
      • TimeOut でメッセージ受信の締切を設定可能
      • タイムアウト時に timeout メッセージが送信される
        • タイムアウトメッセージは handle_info/2 で処理する
      • 普通は erlang:start_time/3 などで手動制御するほうが良い
    • {ok, State, hibernate}
      • メッセージ受信待ちの間、処理能力を犠牲にして省メモリ化する
      • メッセージ受信の間隔が長くなると予想される場合に使うこともある
    • {stop, Reason}
      • 処理失敗時に返す
    • ignore

handle_call/3

同期メッセージの処理関数

  • 引数
    • Request
    • From
    • State
  • 返り値
    • {reply, Reply, NewState}
    • {reply, Reply, NewState, TimeOut}
    • {reply, Reply, NewState, hibernate}
    • {noreply, NewState}
    • {noreply, NewState, TimeOut}
    • {noreply, NewState, hibernate}
    • {stop, Reason, Reply, NewState}
    • {stop, Reason, NewState}

  • TimeOut, hibernate の役割は init/1 と同じ。
  • noreply のときは自分で返信処理を実装する
    • 返信を遅延させたいときなど
    • 返信処理を忘れるとタイムアウトしてクラッシュする
    • 返信処理には gen_server:reply/2 を使う

handle_cast/2

非同期メッセージの処理関数

  • 引数
    • Message
    • State
  • 返り値
    • {noreply, NewState}
    • {noreply, NewState, TimeOut}
    • {noreply, NewState, hibernate}
    • {stop, Reason, NewState}

handle_info/2

handle_call/3handle_cast/2 で予期していないメッセージを処理する関数
! 演算子で直接送られてきたメッセージや、 init/1timeout、モニターからの通知、 EXITシグナルなどを処理する

  • 引数
    • Message
    • State
  • 返り値
    • {noreply, NewState}
    • {noreply, NewState, TimeOut}
    • {noreply, NewState, hibernate}
    • {stop, Reason, NewState}

termminate/2

プロセスの終了処理を実装する

  • 以下のタイミングで呼ばれる
    • handle_* な関数が {stop, Reason, NewState} or {stop, Reason, Reply, NewState} が返したとき
    • gen_server が終了を補足している場合に、親プロセスが死んだとき
  • 引数
    • Reason
    • State

code_change/3

コードアップグレード処理

  • 引数
    • PreviousVersion
    • State
    • Extra

gen_server の関数

gen_server:start_link/3-4, start/3-4

コールバックモジュールの init/1 を呼び出す

  • 引数
    1. コールバックモジュール
      • 例えば同じモジュール内にコールバック関数が定義されていたら ?MODULE と指定すればよい
    2. init/1 にわたすEralng項
    3. 実行中のサーバーのデバッグオプション
    4. {local, 名前} せサーバーの登録名を指定できる

呼び出し例

gen_server:start_link(?MODULE, [], []).

gen_server:call/2-3

コールバックモジュールの handle_call/3 を呼び出す

  • 引数
    • サーバープロセスのPid
    • handle_call にわたすErlang項
    • タイムアウト
      • 指定なしの場合はデフォルトで5秒に設定される
      • タイムアウト時はクラッシュする
      • infinity 指定でタイムアウト無し

呼び出し例

gen_server:call(Pid, {order, Name, Color}).

gen_server:cast/2

コールバックモジュールの handle_cast/2 を呼び出す

  • 引数
    • サーバープロセスのPid
    • handle_cast にわたすErlang項

呼び出し例

gen_server:cast(Pid, {return, State}).
10
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
10