[erlang-questions] tcp connections dropped in gen_server

Ladislav Lenart lenartlad@REDACTED
Wed Sep 7 16:15:52 CEST 2011


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

  * 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 %%%

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