無題の備忘録

IT技術について調べたことや学んだこと、試したこと記録するブログです。Erlang、ネットワーク、 セキュリティ、Linux関係のものが多いです。

Erlang のスタンドアローンの gen_server を停止する

Erlang -- gen_server Behaviour にあるスタンドアローンでの gen_server の停止を試してみたいと思います。

gen_server のサンプルコードとして sample_queue.erl というファイル名で下記のコードを作成します。 このコードは、gen_server のビヘイビアを使って、キューの機能を提供します。

in/1関数で gen_server にアイテムを登録して、out/0関数で gen_server からアイテムを取得します。stop/0関数で gen_server を停止します。

-module(sample_queue).
-behaviour(gen_server).

-export([start_link/0, in/1, out/0, stop/0]).
-export([init/1, handle_call/3, handle_cast/2, terminate/2]).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

in(Item) ->
    gen_server:call(?MODULE, {in, Item}).

out() ->
    gen_server:call(?MODULE, out).

stop() ->
    gen_server:cast(?MODULE, stop).


init(_Args) ->
    {ok, queue:new()}.

handle_call({in, Item}, _From, Queue) ->
    NewQueue = queue:in(Item, Queue),
    {reply, ok, NewQueue};

handle_call(out, _From, Queue) ->
    case queue:out(Queue) of
        {{value, Item}, NewQueue} -> {reply, Item, NewQueue};
        {empty, NewQueue} -> {reply, empty, NewQueue}
    end.

handle_cast(stop, Queue) ->
    {stop, normal, Queue}.

terminate(normal, _State) ->
    timer:sleep(1000),
    io:format("clean up done.~n"),
    ok.

下記は使用例です。下記では、sample_queue モジュールの gen_server を起動し、その後、sample_queue モジュールのAPIを使って、a, b, c というアイテムを登録し、その後はキューのアイテムを空になるまで取り出しています。最後に、gen_server を停止します。

> c(sample_queue).
{ok,sample_queue}
> sample_queue:start_link().
{ok,<0.86.0>}
> sample_queue:in(a).
ok
> sample_queue:in(b).
ok
> sample_queue:in(c). 
ok
> sample_queue:out().
a
> sample_queue:out().
b
> sample_queue:out().
c
> sample_queue:out().
empty
> sample_queue:stop().
ok
clean up done.

stop/0関数は gen_server:cast関数を呼び出しており、非同期リクエストです。

stop() ->
    gen_server:cast(?MODULE, stop).

そのため、sample_queue:stop() を呼び出すとすぐに ok が返ってきますが、終了処理は続いています。Erlang シェルで okの後に clean up done. が表示されているのはそのためです。

> sample_queue:stop().
ok
clean up done.

話は前後しますが、gen_server:cast(?MODULE, stop). の非同期リクエストは、下記のコールバック関数を呼び出し、

handle_cast(stop, Queue) ->
    {stop, normal, Queue}.

この関数の返り値を {stop, normal, Queue} とすることで、下記のように terminate(Reason, State) の Reason を normal とした関数にマッチします。後始末が必要な gen_server であればこの teminate/2 関数に後始末の処理を記述します。

terminate(normal, _State) ->
    timer:sleep(1000),
    io:format("clean up done.~n"),
    ok.