Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Erlang: Avoiding race condition with gen_tcp:controlling_process

Tags:

erlang

I am implementing simple tcp server with the following sequence:

{ok, LS} = gen_tcp:listen(Port,[{active, true}, {reuseaddr, true}, {mode, list}]),
{ok, Socket} =  gen_tcp:accept(LS),
Pid = spawn_link(M, F, [Socket]),           
gen_tcp:controlling_process(Socket, Pid) 

Using the option {active, true} might cause a race condition where a new packet arrives on the socket process before the "controlling_process" get called , which would result in {tcp,Socket,Data} message arriving to the father proccess instead of the child.

How this could be avoided ?

like image 354
GabiMe Avatar asked Jul 10 '12 08:07

GabiMe


1 Answers

There absolutely is a race condition. I just encountered it, today, in OTP 21.2, and that's why I'm here. A packet can arrive between the time that accept returns and the time that inet:tcp_controlling_process sets the socket to passive.

I just wanted to point out tiny simplification to @Keynslug's answer above. The socket can be set to active from a non-owning process, so the ack messaging and enter_loop are unnecessary

-define(TCP_OPTIONS, [binary, {active, false}, ...]).

...

start(Port) ->
    {ok, Socket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
    accept(Socket).

accept(ListenSocket) ->
    case gen_tcp:accept(ListenSocket) of
        {ok, Socket} ->
            Pid = spawn(fun() ->
                io:format("Connection accepted ~n", []),
                loop(Socket)
            end),
            gen_tcp:controlling_process(Socket, Pid),
            inet:setopts(Socket, [{active, once}]),
            accept(ListenSocket);
        Error ->
            exit(Error)
    end.

loop(Sock) ->
    %% set soscket options to receive messages directly into itself
    inet:setopts(Sock, [{active, once}]),
    receive
        {tcp, Socket, Data} ->
            io:format("Got packet: ~p~n", [Data]),
            ...,
            loop(Socket);
        {tcp_closed, Socket} ->
            io:format("Socket ~p closed~n", [Socket]);
        {tcp_error, Socket, Reason} ->
            io:format("Error on socket ~p reason: ~p~n", [Socket, Reason])
    end.
like image 106
user2793784 Avatar answered Nov 15 '22 23:11

user2793784