Erlang で Apache worker MPM っぽいことを OTP っぽく実装してみた

たけまる / Erlang で Apache worker MPM っぽいこと
accept 待ちのプロセスを supervisor 配下に置いてみました。

tcp_server.hrl

-author('cooldaemon@gmail.com').

-define(MAX_TIME, 60).
-define(SHUTDOWN_WAITING_TIME, 2000).
-define(MAX_ACCEPT, 100).

tcp_server_sup.erl

-module(tcp_server_sup).
-author('cooldaemon@gmail.com').
-behaviour(supervisor).

-include("tcp_server.hrl").

% External API
-export([start_link/3, stop/0]). 

% Callbacks
-export([init/1]). 

% External API
start_link(Port, MaxConnection, Callback) ->
  supervisor:start_link(
    {local, ?MODULE}, ?MODULE,
    [Port, MaxConnection, Callback]
  ).

stop() ->
  case whereis(?MODULE) of
    Pid when pid(Pid) ->
      exit(Pid, shutdown),
      ok;
    _ -> not_started
  end.

% Callbacks
init([Port, MaxConnection, Callback]) ->
  case gen_tcp:listen(
    Port, [{active, false}, binary, {packet, line}, {reuseaddr, true}]
  ) of
    {ok, ListenSocket} ->
      init_result(MaxConnection, Callback, ListenSocket);
    {error, Reason} ->
      {stop, Reason}
  end.

% Internal Functions
init_result(MaxConnection, Callback, ListenSocket) ->
  {ok, {{one_for_one, ?MAX_RESTART, ?MAX_TIME},
    lists:map(
      fun (X) ->
        {
          list_to_atom("tcp_acceptor_" ++ integer_to_list(X)),
          {tcp_acceptor, start_link, [Callback, ListenSocket]},
          permanent,
          ?SHUTDOWN_WAITING_TIME,
          worker,
          [tcp_acceptor]
        }
      end,
      lists:seq(1, MaxConnection)
    )
  }}.

tcp_acceptor.erl

-module(tcp_acceptor).
-author('cooldaemon@gmail.com').

% External API
-export([start_link/2]). 

% Callbacks
-export([init/3, accept/2]).

% External API
start_link(Callback, ListenSocket) ->
  proc_lib:start_link(?MODULE, init, [self(), Callback, ListenSocket]).

% Callbacks
init(Parent, Callback, ListenSocket) ->
  proc_lib:init_ack(Parent, {ok, self()}),
  accept(Callback, ListenSocket).

accept(Callback, ListenSocket) ->
  {ok, Socket} = gen_tcp:accept(ListenSocket),
  case Callback of
    {Module, Function} -> Module:Function(Socket);
    _                  -> Callback(Socket)
  end,
  accept(Callback, ListenSocket).

echo.erl

-module(echo).
-author('cooldaemon@gmail.com').

% Callbacks
-export([main/1]).

main(Socket) ->
  case gen_tcp:recv(Socket, 0) of
    {ok, <<"bye\r\n">>} ->
      gen_tcp:send(Socket, <<"cya\r\n">>),
      gen_tcp:close(Socket);
    {ok, Data} ->
      gen_tcp:send(Socket, Data),
      main(Socket);
    {error, closed} ->
      ok;
    {error, Reason} ->
      exit({gen_tcp_recv_error, Reason})
  end.

echo.erl を差し替える事で、用途に応じた処理が可能です。
gen_tcp:listen/2 のオプションを設定できるようにしたり、現在の状態(ポート番号だとか、accept 済みのプロセス数だとか)を保持する gen_server を加えたりした方が、より実用的になる気がしますけど、とりあえず、これくらいで OTP っぽくなってるかなぁと。

使い方は、下記の通り。

$ erl
1> tcp_server_sup:start_link(11211, 2, {echo, main}).

例のごとく、添削熱烈歓迎。