[erlang-questions] tcp connections dropped in gen_server
Ladislav Lenart
lenartlad@REDACTED
Wed Sep 7 16:15:52 CEST 2011
Hello.
On 7.9.2011 12:55, Reynaldo Baquerizo wrote:
>> [I hope it's ok that I reply to the list - someone else might find
>> this information useful as well. I am mentioning this only because
>> you keep replying to me privately.]
>
> Ooops, apologies ... I certainly didn't mean to write to you alone.
No problem :-)
>> It makes very little sense to restart these processes (to me), because
>> the TCP connection will die as well. The external client can reconnect
>> and start anew. Or am I missing something?
>
> Indeed, the client will reconnect. But I think I found the problem. The
> process that is listening for new connections in gen_tcp:accept/1 dies
> at some point, all other processes with established connections are fine
> but eventually crash (cause of bad input), no further reconnections
> will be possible.
I see. Can you provide us with the exact cause of it (i.e. what
gen_tcp:accept/1 returned)? It seems to me that under normal
operation (if the accept was successful at least once before),
there should be no problem of this kind...
> How can I isolate the listening process? or reestructure to restart it
> if it crashes?
You need to restructure the processes then:
port (service) supervisor - one_to_one
acceptor - worker
session supervisor - simple_one_to_one
connection - worker
Notes:
* The above is a complete hierarchy of one TCP service listening on
a given port.
* There's exactly one acceptor under a port supervisor. The acceptor
creates a listening socket and calls gen_tcp:accept/1 on it. It's
terminated via brutal_kill (because of the blocking nature of the
gen_tcp:accept/1 call). If it crashes it will be restarted by the
port supervisor.
* session_supervisor is basically a tcp_sup.
* connection is essentially the loop(Socket) part of the previous
connection process. These don't have to be killed via brutal_kill
because they don't block.
All the "magic" happens in the acceptor process...
%%%%%%%%%%%%%%%%%%%%
%%% TCP acceptor %%%
%%%%%%%%%%%%%%%%%%%%
-module(tcp_acceptor).
start_link(SupPid, Port) ->
{ok, proc_lib:spawn_link(?MODULE, init, [SupPid, Port])}.
init(SupPid, Port) ->
%% IMPORTANT: active must be set to false.
{ok, ListenSocket} = gen_tcp:listen(Port, [binary, {packet, 0}, {reuseaddr, true}, {active, false}]),
accept(SupPid, ListenSocket).
accept(SupPid, ListenSocket) ->
{ok, Socket} = gen_tcp:accept(ListenSocket),
start_connection(SupPid, Socket),
accept(SupPid, ListenSocket).
start_connection(SupPid, Socket) ->
%% tcp_session_sup is the id of the simple_one_to_one session supervisor
%% in the child specification of the port supervisor (SupPid here).
Kids = supervisor:which_children(SupPid),
{value, {tcp_session_sup, SessionSup, _, _}} = lists:keysearch(tcp_session_sup, 1, Kids),
{ok, Pid}} = supervisor:start_child(SessionSup, []),
%% Force Socket to send future messages to Pid and not to me.
ok = gen_tcp:controlling_process(Socket, Pid),
%% Inform Pid about Socket (let it initialize the TCP session).
%% As a bare minimum Pid should remember Socket in its internal state
%% and set active flag to one of {active, true} or {active, once}.
Pid ! {init_tcp_session, Socket}.
I hope the rest of the picture is clear now. Note also that this can be
turned into a generic TCP service / application fairly easily. You just
need to parametrize the above with:
* CallbackModule - name of the module that implements application specific
connection process on top of TCP. Session supervisor needs it to create
a desired child specification.
* CallbackOptions - list of initial arguments passed to connection process
via message {init_tcp_session, Socket, CallbackOptions}.
* PacketOptions - list of PacketOptions passed to tcp_acceptor.
If you have further questions, please do not hesitate and ask! :-)
Ladislav Lenart
More information about the erlang-questions
mailing list