[erlang-questions] Socket acceptor strategy: pooling or async

Andrew Tereskin <>
Thu Apr 4 12:24:19 CEST 2013


Hi everyone,

I'm trying to what is the best strategy for tcp socket acceptors. Just
after brief research I found 2 approaches:

1. Pool of acceptors that can work in parallel. According to documentation
ListenSocket can be used not only in the process where this socket was
created.

Example code (from http://learnyousomeerlang.com/buckets-of-sockets):

%%% The supervisor in charge of all the socket acceptors.
-module(sockserv_sup).
-behaviour(supervisor).

init([]) ->
    .....
    {ok, ListenSocket} = gen_tcp:listen(Port, [{active,once},
{packet,line}]),
    spawn_link(fun empty_listeners/0),
    {ok, {{simple_one_for_one, 60, 3600},
         [{socket,
          {sockserv_serv, start_link, [ListenSocket]}, % pass the socket!
          temporary, 1000, worker, [sockserv_serv]}
         ]}}.

start_socket() -> supervisor:start_child(?MODULE, []).

empty_listeners() -> [start_socket() || _ <- lists:seq(1,20)], ok.

%%%%%%%%%%%%%%%%%%
%% socket acceptor

-module(sockserv_serv).
-behaviour(gen_server).

init(Socket) ->
    ....
    gen_server:cast(self(), accept),
    {ok, #state{socket=Socket}}.

handle_call(_E, _From, State) ->  {noreply, State}.

handle_cast(accept, S = #state{socket=ListenSocket}) ->
    {ok, AcceptSocket} = gen_tcp:accept(ListenSocket),
    sockserv_sup:start_socket(), % a new acceptor is born, praise the lord
    {noreply, S#state{socket=AcceptSocket, next=name}};
---------------------------------

As I examined code of mochiweb (
https://github.com/mochi/mochiweb/blob/master/src/mochiweb_socket_server.erl,
https://github.com/mochi/mochiweb/blob/master/src/mochiweb_acceptor.erl)
and ranch (https://github.com/extend/ranch) they use the same approach.

2. Using undocumented function prim_inet:async_accept

Example code (from
http://www.trapexit.org/Building_a_Non-blocking_TCP_server_using_OTP_principles
)

init([Port, Module]) ->
    process_flag(trap_exit, true),
    Opts = [binary, {packet, 2}, {reuseaddr, true},
            {keepalive, true}, {backlog, 30}, {active, false}],
    case gen_tcp:listen(Port, Opts) of
    {ok, Listen_socket} ->
        %%Create first accepting process
        {ok, Ref} = prim_inet:async_accept(Listen_socket, -1),
        {ok, #state{listener = Listen_socket,
                    acceptor = Ref,
                    module   = Module}};
    {error, Reason} ->
        {stop, Reason}
    end.



handle_info({inet_async, ListSock, Ref, {ok, CliSocket}},
            #state{listener=ListSock, acceptor=Ref, module=Module} = State) ->
    try
        case set_sockopt(ListSock, CliSocket) of
        ok              -> ok;
        {error, Reason} -> exit({set_sockopt, Reason})
        end,

        %% New client connected - spawn a new process using the
simple_one_for_one
        %% supervisor.
        {ok, Pid} = tcp_server_app:start_client(),
        gen_tcp:controlling_process(CliSocket, Pid),
        %% Instruct the new FSM that it owns the socket.
        Module:set_socket(Pid, CliSocket),

        %% Signal the network driver that we are ready to accept
another connection
        case prim_inet:async_accept(ListSock, -1) of
        {ok,    NewRef} -> ok;
        {error, NewRef} -> exit({async_accept, inet:format_error(NewRef)})
        end,

        {noreply, State#state{acceptor=NewRef}}
    catch exit:Why ->
        error_logger:error_msg("Error in async accept: ~p.\n", [Why]),
        {stop, Why, State}
    end;


It is used by erlyvideo (
https://github.com/erlyvideo/erlyvideo/blob/master/apps/erlyvideo/src/core/gen_listener.erl
).

Can you give some pros and cons of both approaches?

With best wishes,
Andrey Tereskin
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20130404/821df153/attachment.html>


More information about the erlang-questions mailing list