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

Sean Hinde sean.hinde@REDACTED
Sat Feb 16 15:43:49 CET 2008


Telecom systems typically have to allow re-configuration on the fly of  
everything (e.g. by sending a reconf message to a process). They might  
also perform hearbeat monitoring of key processes. Both of these  
things are much easier if a process is not sitting in a long term  
blocking call.

Sean

On 16 Feb 2008, at 03:13, Samuel Tesla wrote:

> 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
>
>
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://www.erlang.org/mailman/listinfo/erlang-questions




More information about the erlang-questions mailing list