【问题标题】:Erlang: Avoiding race condition with gen_tcp:controlling_processErlang:使用 gen_tcp:controlling_process 避免竞争条件
【发布时间】:2020-01-06 06:18:05
【问题描述】:

我正在使用以下序列实现简单的 tcp 服务器:

{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) 

使用选项 {active, true} 可能会导致竞争条件,即新数据包在调用“controlling_process”之前到达套接字进程,这将导致 {tcp,Socket,Data} 消息到达父进程而不是孩子。

如何避免这种情况?

【问题讨论】:

  • 看起来不会存在竞争条件,因为在调用 controlling_process 之前没有任何内容从套接字读取任何内容。所以,你问题中的可能这个词让我很困扰。你真的见过比赛条件发生吗?如果是这样,您能否提供更多详细信息?
  • {active, true} 标志意味着(如果我理解正确的话)系统主动从套接字读取数据包并将它们作为 {tcp,Socket,Data} 发送到控制进程消息收件箱。
  • 我明白你的意思。我建议将呼叫从您生成的进程(在您的示例中为M:F)移至gen_tcp:accept 以避免这种情况。这样您就可以完全避免致电gen_tcp:controlling_process
  • @Keynslug 在我还在打字的时候在下面发布了一个很好的答案。

标签: erlang


【解决方案1】:

你是对的。在这种情况下,您肯定需要在侦听套接字选项之间传递{active, false}。考虑一下这段代码:

-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", []),
                enter_loop(Socket)
            end),
            gen_tcp:controlling_process(Socket, Pid),
            Pid ! ack,
            accept(ListenSocket);
        Error ->
            exit(Error)
    end.

enter_loop(Sock) ->
    %% make sure to acknowledge owner rights transmission finished
    receive ack -> ok end,
    loop(Sock).

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.

因此,在controlling_process 成功之前,您不会丢失任何东西。已知问题已在互联网上进行了很多讨论。 如果您希望使用准备就绪的解决方案,您肯定需要查看Ranch 项目。

【讨论】:

  • 刚刚发现到loop 的任何尾递归都会冻结在receive ack -> ok end 上。我将发布编辑以解决此问题。
  • 顺便说一句,{active, once} 而不是 {active, true} 的原因是什么?
  • 给出简单,防止消息进程收件箱泛滥。有一个重要的概念——确保你没有使用{active, true}。许多人提到这一点。如果您使用遵循 HTTP 等请求-响应模型的东西,那么 {active, once} 就是您所需要的。因此,我们提供了保证——如果机器处于高负载下,VM 不会超出可预测的内存占用。如果您使用流式传输大量数据,您肯定需要继续使用{active, false} 并手动执行recvs。鉴于这种情况,这种方式将大大提高性能和系统带宽。
【解决方案2】:

如果套接字处于活动状态,inet:tcp_controlling_process(由gen_tcp:controlling_process 调用)将套接字设置为被动,然后选择性地接收与该套接字相关的所有消息并将它们发送给新所有者,有效地将它们移动到新所有者的消息中队列。然后它将套接字恢复为活动状态。

所以不存在竞争条件:他们已经想到了这一点并在库中修复了它。

【讨论】:

  • 感谢您指出这一点。我想知道这是否是(相对)最近的功能(鉴于这个问题的旧答案以及互联网上的许多类似讨论)?
【解决方案3】:

绝对存在竞争条件。我今天在 OTP 21.2 中遇到了它,这就是我在这里的原因。数据包可以在accept 返回和inet:tcp_controlling_process 将套接字设置为被动的时间之间到达。

我只是想指出上面@Keynslug 答案的微小简化。套接字可以从非拥有进程设置为活动的,因此不需要ack 消息传递和enter_loop

-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.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-30
    • 2010-09-25
    • 2017-12-02
    • 2019-01-10
    • 1970-01-01
    • 1970-01-01
    • 2010-09-25
    相关资源
    最近更新 更多