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

Samuel Tesla samuel@REDACTED
Sat Feb 16 04:13:05 CET 2008


When is it not an acceptable design?

I can't imagine that it's performance.  Both of the large examples of TCP
services that I've seen in Erlang (YAWS and ejabberd) use this model, and
they're both documented to handle very heavy loads and perform very well.

-- Samuel

On Fri, Feb 15, 2008 at 8:25 PM, Serge Aleynikov <saleyn@REDACTED> wrote:

> The objective of the article was to show how to build a TCP server using
> only non-blocking processes and relying on the network driver to deliver
> events of interest to the application layer.  Blocking processes for N
> seconds as in the code below is not always an acceptable design.
>
> I also wish that the non-blocking prim_inet:accept/2 call was exposed to
> the gen_tcp module.  Perhaps this was an oversight rather than a
> conscious decision?
>
> Serge
>
> Samuel Tesla wrote:
> > 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
> >>
> >
> >
> > ------------------------------------------------------------------------
> >
> > _______________________________________________
> > 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/955187c8/attachment.htm>


More information about the erlang-questions mailing list