[erlang-questions] blocking vs non-blocking gen_tcp:accept()

Samuel Tesla samuel@REDACTED
Fri Feb 15 21:54:05 CET 2008


Robin: Sorry if you got this twice, I realized just after I hit send that I
had only replied to you.

It only blocks the Erlang process, I believe.

I've been meaning to post some code examples about this.

What's always bugged me about that article is that it relies on prim_inet
which is not guaranteed at all to remain the same across releases, whereas
gen_tcp is (or at least guaranteed to be squawked about).  I've written
several TCP servers for personal projects, and I have found that there's no
need to use anything but gen_tcp:accept (with a timeout).

Here's an example of using gen_tcp:accept/2 in a gen_server that operates
similarly to the one in that walkthrough.

%% BEGIN CODE %%
-module(tcp_lister).

-behaviour(gen_server).

-export([start_link/3]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

-define(TCP_ACCEPT_TIMEOUT, 10000).

-record(state, {
          listen, % listening socket
          sup     % supervisor for client proxies
         }).

start_link(Port, Opts, Sup) when is_integer(Port),
                                 is_list(Opts),
                                 is_atom(Sup) ->
    gen_server:start_link(?MODULE, [Port, Opts, Sup], []).

init([Port, Opts, Sup]) ->
    {ok, Listen} = gen_tcp:listen(Port, Opts),
    State = #state{listen=Listen, sup=Sup},
    gen_server:cast(self(), accept),
    ?MODULE:accept(),
    {ok, State}.

handle_call(Request, From, State) ->
    {stop, {unknown_call, {Request, From}}, State}.

handle_cast(accept, State) ->
    #state{listen=Listen, sup=Sup} = State,
    case gen_tcp:accept(Listen, ?TCP_ACCEPT_TIMEOUT) of
        {ok, Socket} -> Sup:start_client(Socket);
        {error, timeout} -> ok
    end,
    gen_server:cast(self(), accept),
    {noreply, State};
handle_cast(Msg, State) ->
    {stop, {unknown_cast, Msg}, State}.

handle_info(Info, State) ->
    {stop, {unknown_info, Info}, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%% END CODE %%

Then Sup:start_link/1 looks like this:

%% BEGIN CODE %%
start_client(Socket) ->
    {ok, Pid} = supervisor:start_child(?MODULE, []),
    gen_tcp:controlling_process(Socket, Pid),
    ok = client_module:set_socket(Pid, Socket),
    ok.
%% END CODE %%

And client_module:set_socket just lets the newly spawned process know what
it's socket is just like in the trapexit article.

There's several things this code doesn't handle. It could get other errors
such as EMFILE or ENFILE from gen_tcp:accept/2. But in those cases the
gen_server will blow up and get logged and the supervisor will do something
appropriate. There is a brief moment during which sockets will sit idle in
the buffer, but seriously, it's brief.

The key thing here is that you're using gen_tcp:accept/2, so you won't be
blocking the process forever. That means that the process will eventually
loop through and check it's mailbox again and can handle all of the fun
supervision-tree stuff gen_server does behind the scenes.

Hope this helps!

-- Samuel

On Fri, Feb 15, 2008 at 2:16 PM, Robin <robi123@REDACTED> wrote:

> Does gen_tcp:accept() block the entire OS thread or just the calling
> erlang process?
>
> "One of the shortcomings of the gen_tcp module is that it only exports
> interface to a blocking accept call." ->
>
> http://www.trapexit.org/Building_a_Non-blocking_TCP_server_using_OTP_principles
>
> Does the performance gain of non-blocking accept justify the added
> complexity (finite state machine etc)?
>
> thanks,
>
> Robin
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://www.erlang.org/mailman/listinfo/erlang-questions
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20080215/7b212ed0/attachment.htm>


More information about the erlang-questions mailing list