When is it not an acceptable design? <br><br>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.<br>
<br>-- Samuel<br><br><div class="gmail_quote">On Fri, Feb 15, 2008 at 8:25 PM, Serge Aleynikov <<a href="mailto:saleyn@gmail.com">saleyn@gmail.com</a>> wrote:<br><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
The objective of the article was to show how to build a TCP server using<br>
only non-blocking processes and relying on the network driver to deliver<br>
events of interest to the application layer. Blocking processes for N<br>
seconds as in the code below is not always an acceptable design.<br>
<br>
I also wish that the non-blocking prim_inet:accept/2 call was exposed to<br>
the gen_tcp module. Perhaps this was an oversight rather than a<br>
conscious decision?<br>
<font color="#888888"><br>
Serge<br>
</font><div><div></div><div class="Wj3C7c"><br>
Samuel Tesla wrote:<br>
> Robin: Sorry if you got this twice, I realized just after I hit send that I<br>
> had only replied to you.<br>
><br>
> It only blocks the Erlang process, I believe.<br>
><br>
> I've been meaning to post some code examples about this.<br>
><br>
> What's always bugged me about that article is that it relies on prim_inet<br>
> which is not guaranteed at all to remain the same across releases, whereas<br>
> gen_tcp is (or at least guaranteed to be squawked about). I've written<br>
> several TCP servers for personal projects, and I have found that there's no<br>
> need to use anything but gen_tcp:accept (with a timeout).<br>
><br>
> Here's an example of using gen_tcp:accept/2 in a gen_server that operates<br>
> similarly to the one in that walkthrough.<br>
><br>
> %% BEGIN CODE %%<br>
> -module(tcp_lister).<br>
><br>
> -behaviour(gen_server).<br>
><br>
> -export([start_link/3]).<br>
> -export([init/1, handle_call/3, handle_cast/2, handle_info/2,<br>
> terminate/2, code_change/3]).<br>
><br>
> -define(TCP_ACCEPT_TIMEOUT, 10000).<br>
><br>
> -record(state, {<br>
> listen, % listening socket<br>
> sup % supervisor for client proxies<br>
> }).<br>
><br>
> start_link(Port, Opts, Sup) when is_integer(Port),<br>
> is_list(Opts),<br>
> is_atom(Sup) -><br>
> gen_server:start_link(?MODULE, [Port, Opts, Sup], []).<br>
><br>
> init([Port, Opts, Sup]) -><br>
> {ok, Listen} = gen_tcp:listen(Port, Opts),<br>
> State = #state{listen=Listen, sup=Sup},<br>
> gen_server:cast(self(), accept),<br>
> ?MODULE:accept(),<br>
> {ok, State}.<br>
><br>
> handle_call(Request, From, State) -><br>
> {stop, {unknown_call, {Request, From}}, State}.<br>
><br>
> handle_cast(accept, State) -><br>
> #state{listen=Listen, sup=Sup} = State,<br>
> case gen_tcp:accept(Listen, ?TCP_ACCEPT_TIMEOUT) of<br>
> {ok, Socket} -> Sup:start_client(Socket);<br>
> {error, timeout} -> ok<br>
> end,<br>
> gen_server:cast(self(), accept),<br>
> {noreply, State};<br>
> handle_cast(Msg, State) -><br>
> {stop, {unknown_cast, Msg}, State}.<br>
><br>
> handle_info(Info, State) -><br>
> {stop, {unknown_info, Info}, State}.<br>
><br>
> terminate(_Reason, _State) -><br>
> ok.<br>
><br>
> code_change(_OldVsn, State, _Extra) -><br>
> {ok, State}.<br>
><br>
> %% END CODE %%<br>
><br>
> Then Sup:start_link/1 looks like this:<br>
><br>
> %% BEGIN CODE %%<br>
> start_client(Socket) -><br>
> {ok, Pid} = supervisor:start_child(?MODULE, []),<br>
> gen_tcp:controlling_process(Socket, Pid),<br>
> ok = client_module:set_socket(Pid, Socket),<br>
> ok.<br>
> %% END CODE %%<br>
><br>
> And client_module:set_socket just lets the newly spawned process know what<br>
> it's socket is just like in the trapexit article.<br>
><br>
> There's several things this code doesn't handle. It could get other errors<br>
> such as EMFILE or ENFILE from gen_tcp:accept/2. But in those cases the<br>
> gen_server will blow up and get logged and the supervisor will do something<br>
> appropriate. There is a brief moment during which sockets will sit idle in<br>
> the buffer, but seriously, it's brief.<br>
><br>
> The key thing here is that you're using gen_tcp:accept/2, so you won't be<br>
> blocking the process forever. That means that the process will eventually<br>
> loop through and check it's mailbox again and can handle all of the fun<br>
> supervision-tree stuff gen_server does behind the scenes.<br>
><br>
> Hope this helps!<br>
><br>
> -- Samuel<br>
><br>
> On Fri, Feb 15, 2008 at 2:16 PM, Robin <<a href="mailto:robi123@gmail.com">robi123@gmail.com</a>> wrote:<br>
><br>
>> Does gen_tcp:accept() block the entire OS thread or just the calling<br>
>> erlang process?<br>
>><br>
>> "One of the shortcomings of the gen_tcp module is that it only exports<br>
>> interface to a blocking accept call." -><br>
>><br>
>> <a href="http://www.trapexit.org/Building_a_Non-blocking_TCP_server_using_OTP_principles" target="_blank">http://www.trapexit.org/Building_a_Non-blocking_TCP_server_using_OTP_principles</a><br>
>><br>
>> Does the performance gain of non-blocking accept justify the added<br>
>> complexity (finite state machine etc)?<br>
>><br>
>> thanks,<br>
>><br>
>> Robin<br>
>> _______________________________________________<br>
>> erlang-questions mailing list<br>
>> <a href="mailto:erlang-questions@erlang.org">erlang-questions@erlang.org</a><br>
>> <a href="http://www.erlang.org/mailman/listinfo/erlang-questions" target="_blank">http://www.erlang.org/mailman/listinfo/erlang-questions</a><br>
>><br>
><br>
><br>
</div></div>> ------------------------------------------------------------------------<br>
<div><div></div><div class="Wj3C7c">><br>
> _______________________________________________<br>
> erlang-questions mailing list<br>
> <a href="mailto:erlang-questions@erlang.org">erlang-questions@erlang.org</a><br>
> <a href="http://www.erlang.org/mailman/listinfo/erlang-questions" target="_blank">http://www.erlang.org/mailman/listinfo/erlang-questions</a><br>
<br>
</div></div></blockquote></div><br>