[erlang-questions] <Unverified Sender>Re: {error, closed} vs. {error, econnreset}

Musumeci, Antonio S Antonio.Musumeci@REDACTED
Tue May 5 13:06:38 CEST 2015


It feels like there really should be two code paths. One lower level which mimics traditional socket behavior and then something on top which may or may not hide some of the details on account of the abstraction. Masking the errors here can cause a lot of useful info to be lost.

-----Original Message-----
From: erlang-questions-bounces@REDACTED [mailto:erlang-questions-bounces@REDACTED] On Behalf Of Bekes, Andras G (ICT)
Sent: Tuesday, May 05, 2015 3:45 AM
To: Rory Byrne; erlang-questions@REDACTED
Subject: <Unverified Sender>Re: [erlang-questions] {error,closed} vs. {error,econnreset}

Note: This e-mail has been flagged because although the sender appears to be internal, the e-mail originated outside the Firm. The sender’s address may have been forged, which can be an indicator of malicious activity. If you have any concerns about the content or sender of this e-mail, please report it immediately at http://irespond.

Thank you very much for your efforts Rory.

The ability "to set a socket option that shows all econnreset errors" sounds like the right solution. I am wondering why hiding this detail is the default, but I believe there were good enough reasons to design it that way.

I accept that your solution will not notice the connection reset event in some corner cases. I think this will not apply in my case: I am sending a small amount of data (<1KB) and wait for the reply.

I am looking forward to see your patch in the next release of Erlang/OTP!

Thanks,
   Andras

-----Original Message-----
From: erlang-questions-bounces@REDACTED [mailto:erlang-questions-bounces@REDACTED] On Behalf Of Rory Byrne
Sent: Saturday, May 02, 2015 12:32 PM
To: erlang-questions@REDACTED
Subject: Re: [erlang-questions] {error,closed} vs. {error,econnreset}

Hi Andras,

On Fri, May 01, 2015 at 11:53:13AM +0000, Bekes, Andras G wrote:
>
> Please see the trace in the attached file. Do you have suggestions on what else shall I trace?
>
>>> On 2015-02-10 14:39, Bekes, Andras G wrote:
>>>
>>> Looks like I have a problem with the erroneous result of gen_tcp:recv/2, as it returns {error,closed} instead of my expectation of {error,econnreset}.
>>>
...
>>>
>>> Unfortunately it looks like I really need to separate these two results.
>>>
>>> I also tried gen_tcp in active mode, but no difference, the result is {error,closed} instead of {error,econnreset}.
>>>
>>> Can someone explain why the econnreset error is masked? Is there any way I can separate the two kinds of events?
>>>

It seems it was a design decision to mask both ECONNRESET recv errors and
all send errors. You can see where this happens for recv errors at around
line 9988 in erts/emulator/drivers/common/inet_drv.c. Open that file and
search for ECONNRESET and you will find the following section of code.

    n = sock_recv(desc->inet.s, desc->i_ptr, nread, 0);

    if (IS_SOCKET_ERROR(n)) {
        int err = sock_errno();
        if (err == ECONNRESET) {
            DEBUGF((" => detected close (connreset)\r\n"));
            return tcp_recv_closed(desc);
        }
        if (err == ERRNO_BLOCK) {
            DEBUGF((" => would block\r\n"));
            return 0;
        }
        else {
            DEBUGF((" => error: %d\r\n", err));
            return tcp_recv_error(desc, err);
        }
    }
    else if (n == 0) {
        DEBUGF(("  => detected close\r\n"));
        return tcp_recv_closed(desc);
    }

As you can see from this, ECONNRESET is being treated as though it's an EOF
rather than an error: both end up calling tcp_recv_closed(). As a quick fix to
your problem, change the code to read:

        if (err == ECONNRESET) {
            DEBUGF((" => detected close (connreset)\r\n"));
            return tcp_recv_error(desc, err);
        }

However, this is an indiscriminate change which will effect all socket code,
including the networking code for distributed Erlang, and any third party
Erlang code you are using.

Also, be warned that this fix won't alert you to every incoming RST. For
example, if you have a large payload buffered in the socket driver in Erlang
(i.e. a payload that is too large for the kernel socket send buffer) and you
receive an RST, then your next call to gen_tcp:recv/2 will return
{error, closed} rather than {error, econnreset}. See below for example code
which shows this. The reason this happens is as follows:

 1. When there is outbound data buffered in the socket driver queue,
    gen_tcp:send/2 becomes an asynchronous call. Which is to say, the new data
    is added to the driver queue and the call returns 'ok'. Then the actual
    send syscall takes place at a later time, according to epoll, select or
    whatever.

 2. Then, when you are in passive mode and an error is detected on the send
    syscall there is no caller to return it to, so it is marked as a
    "delayed close error". This has two consequences:

      (a) it is masked to a generic {error, closed}; and
      (b) it is returned on the next call to gen_tcp:recv/2 or gen_tcp:send/2.

So, the send error is ultimately returned on a gen_tcp:recv/2 call, and all
send errors are masked as {error, closed}.

In active mode the problems are similar.

I've got a patch for the recv errors and I'm working on a patch for the send
errors. Both patches will allow the user to set a socket option that shows all
econnreset errors (changing some epipe errors to econnreset in the case of send
errors). With any luck, I'll release these patches over the next week or
two as part of a larger set of patches. No guarantee they'll be accepted
though.

Rory

%%--- Server Code ---
-module(server).
-export([run/0]).

-define(PORT, 7777).
-define(RECBUF, (4 * 1024)).

run() ->
    SockOpts = [{recbuf, ?RECBUF}, {reuseaddr, true}, {active, false}],
    {ok, LSock} = gen_tcp:listen(?PORT, SockOpts),
    {ok, Sock} = gen_tcp:accept(LSock),
    ok = gen_tcp:close(LSock),
    ok = inet:setopts(Sock, [{linger, {true, 0}}]),
    ok = gen_tcp:send(Sock, "Any payload"),
    timer:sleep(1000),
    ok = gen_tcp:close(Sock).

%%--- Client Code ---
-module(client).
-export([run/0]).

-define(PORT, 7777).
-define(SNDBUF, (4 * 1024)).
-define(PAYLOAD_SIZE, (30 * 1024)).

run() ->
    SockOpts = [{sndbuf, ?SNDBUF}, {active, false}],
    {ok, Sock} = gen_tcp:connect("localhost", ?PORT, SockOpts),
    Payload = lists:duplicate(?PAYLOAD_SIZE, $.),
    ok = gen_tcp:send(Sock, Payload),
    ok = timer:sleep(2000),
    Res = gen_tcp:recv(Sock, 0),
    io:format("Result: ~p~n", [Res]),
    gen_tcp:close(Sock).
_______________________________________________
erlang-questions mailing list
erlang-questions@REDACTED
http://erlang.org/mailman/listinfo/erlang-questions


_______________________________________________
erlang-questions mailing list
erlang-questions@REDACTED
http://erlang.org/mailman/listinfo/erlang-questions




More information about the erlang-questions mailing list