From 4f63e427f1345b40b484ef16f6ff5e922bf14dac Mon Sep 17 00:00:00 2001
From: Rory Byrne
Date: Mon, 20 Apr 2015 10:57:53 +0100
Subject: Add 'show_econnreset' TCP socket option
An ECONNRESET is a socket error which tells us that a TCP peer has sent
an RST. The RST indicates that they have aborted the connection and
that the payload we have received should not be considered complete. Up
until now, the implementation of TCP in inet_drv.c has hidden the
receipt of the RST from the user, treating it as though it was just
a FIN terminating the read side of the socket.
There are many cases where user code needs to be able to distinguish
between a socket that was closed normally and one that was aborted.
Setting the option {show_econnreset, true} enables the user to receive
ECONNRESET errors on both active and passive sockets.
A connected socket returned from gen_tcp:accept/1 will inherit the
show_econnreset setting of the listening socket.
By default this option is set to {show_econnreset, false}.
Note that this patch only enables the reporting of ECONNRESET when
the socket is being read from. It does not report ECONNRESET (or
EPIPE) when the user tries to write to a connection when an RST
has already been received. Currently the TCP implementation in
inet_drv.c hides all such send errors from the user in favour
of returning {error, close}. A separate patch will be needed to
enable the reporting of such errors.
---
lib/kernel/doc/src/inet.xml | 27 +++++++++
lib/kernel/src/gen_tcp.erl | 2 +
lib/kernel/src/inet.erl | 7 ++-
lib/kernel/src/inet_int.hrl | 1 +
lib/kernel/test/gen_tcp_misc_SUITE.erl | 107 +++++++++++++++++++++++++++++++++
5 files changed, 141 insertions(+), 3 deletions(-)
(limited to 'lib/kernel')
diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml
index 77a8caaaf6..a364f9e0c9 100644
--- a/lib/kernel/doc/src/inet.xml
+++ b/lib/kernel/doc/src/inet.xml
@@ -1037,6 +1037,33 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp
+ {show_econnreset, Boolean}(TCP/IP sockets)
+ -
+
When this option is set to false, as it is by
+ default, an RST that is received from the TCP peer is treated
+ as a normal close (as though a FIN was sent). A caller
+ to gen_tcp:recv/2
+ will get {error, closed}. In active
+ mode the controlling process will receive a
+ {tcp_close, Socket} message, indicating that the
+ peer has closed the connection.
+ Setting this option to true will allow you to
+ distinguish between a connection that was closed normally,
+ and one which was aborted (intentionally or unintentionally)
+ by the TCP peer. A call to
+ gen_tcp:recv/2
+ will return {error, econnreset}. In
+ active mode, the controlling process will receive a
+ {tcp_error, Socket, econnreset} message
+ before the usual {tcp_closed, Socket}, as is
+ the case for any other socket error.
+ A connected socket returned from
+ gen_tcp:accept/1
+ will inherit the show_econnreset setting from the
+ listening socket.
+
+
+
{sndbuf, Size}
-
The minimum size of the send buffer to use for the socket.
diff --git a/lib/kernel/src/gen_tcp.erl b/lib/kernel/src/gen_tcp.erl
index bc8ffbe5e3..86251fc8f1 100644
--- a/lib/kernel/src/gen_tcp.erl
+++ b/lib/kernel/src/gen_tcp.erl
@@ -58,6 +58,7 @@
{reuseaddr, boolean()} |
{send_timeout, non_neg_integer() | infinity} |
{send_timeout_close, boolean()} |
+ {show_econnreset, boolean()} |
{sndbuf, non_neg_integer()} |
{tos, non_neg_integer()} |
{ipv6_v6only, boolean()}.
@@ -89,6 +90,7 @@
reuseaddr |
send_timeout |
send_timeout_close |
+ show_econnreset |
sndbuf |
tos |
ipv6_v6only.
diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl
index d668738109..1ae90aaf0c 100644
--- a/lib/kernel/src/inet.erl
+++ b/lib/kernel/src/inet.erl
@@ -654,7 +654,7 @@ options() ->
multicast_if, multicast_ttl, multicast_loop,
exit_on_close, high_watermark, low_watermark,
high_msgq_watermark, low_msgq_watermark,
- send_timeout, send_timeout_close
+ send_timeout, send_timeout_close, show_econnreset
].
%% Return a list of statistics options
@@ -672,7 +672,8 @@ connect_options() ->
[tos, priority, reuseaddr, keepalive, linger, sndbuf, recbuf, nodelay,
header, active, packet, packet_size, buffer, mode, deliver,
exit_on_close, high_watermark, low_watermark, high_msgq_watermark,
- low_msgq_watermark, send_timeout, send_timeout_close, delay_send, raw].
+ low_msgq_watermark, send_timeout, send_timeout_close, delay_send, raw,
+ show_econnreset].
connect_options(Opts, Family) ->
BaseOpts =
@@ -740,7 +741,7 @@ listen_options() ->
header, active, packet, buffer, mode, deliver, backlog, ipv6_v6only,
exit_on_close, high_watermark, low_watermark, high_msgq_watermark,
low_msgq_watermark, send_timeout, send_timeout_close, delay_send,
- packet_size, raw].
+ packet_size, raw, show_econnreset].
listen_options(Opts, Family) ->
BaseOpts =
diff --git a/lib/kernel/src/inet_int.hrl b/lib/kernel/src/inet_int.hrl
index 889b596a22..7bd973af99 100644
--- a/lib/kernel/src/inet_int.hrl
+++ b/lib/kernel/src/inet_int.hrl
@@ -147,6 +147,7 @@
-define(INET_LOPT_MSGQ_HIWTRMRK, 36).
-define(INET_LOPT_MSGQ_LOWTRMRK, 37).
-define(INET_LOPT_NETNS, 38).
+-define(INET_LOPT_TCP_SHOW_ECONNRESET, 39).
% Specific SCTP options: separate range:
-define(SCTP_OPT_RTOINFO, 100).
-define(SCTP_OPT_ASSOCINFO, 101).
diff --git a/lib/kernel/test/gen_tcp_misc_SUITE.erl b/lib/kernel/test/gen_tcp_misc_SUITE.erl
index 4f0d7a7d50..f08f28b650 100644
--- a/lib/kernel/test/gen_tcp_misc_SUITE.erl
+++ b/lib/kernel/test/gen_tcp_misc_SUITE.erl
@@ -31,6 +31,8 @@
init_per_testcase/2, end_per_testcase/2,
otp_3924/1, otp_3924_sender/4, closed_socket/1,
shutdown_active/1, shutdown_passive/1, shutdown_pending/1,
+ show_econnreset_active/1, show_econnreset_active_once/1,
+ show_econnreset_passive/1,
default_options/1, http_bad_packet/1,
busy_send/1, busy_disconnect_passive/1, busy_disconnect_active/1,
fill_sendq/1, partial_recv_and_close/1,
@@ -92,6 +94,8 @@ all() ->
iter_max_socks, passive_sockets, active_n,
accept_closed_by_other_process, otp_3924, closed_socket,
shutdown_active, shutdown_passive, shutdown_pending,
+ show_econnreset_active, show_econnreset_active_once,
+ show_econnreset_passive,
default_options, http_bad_packet, busy_send,
busy_disconnect_passive, busy_disconnect_active,
fill_sendq, partial_recv_and_close,
@@ -1075,6 +1079,109 @@ shutdown_pending(Config) when is_list(Config) ->
gen_tcp:close(S)
end.
+%%
+%% Test 'show_econnreset' option
+%%
+
+show_econnreset_active(Config) when is_list(Config) ->
+ %% First confirm everything works with option turned off.
+ {ok, L} = gen_tcp:listen(0, []),
+ {ok, Port} = inet:port(L),
+ {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]),
+ {ok, S} = gen_tcp:accept(L),
+ ok = gen_tcp:close(L),
+ ok = inet:setopts(Client, [{linger, {true, 0}}]),
+ ok = gen_tcp:close(Client),
+ receive
+ {tcp_closed, S} ->
+ ok;
+ Other ->
+ ?t:fail({unexpected1, Other})
+ after 1000 ->
+ ?t:fail({timeout, {server, no_tcp_closed}})
+ end,
+
+ %% Now test with option switched on.
+ %% Note: We are also testing that the show_econnreset option is
+ %% inherited from the listening socket by the accepting socket.
+ {ok, L1} = gen_tcp:listen(0, [{show_econnreset, true}]),
+ {ok, Port1} = inet:port(L1),
+ {ok, Client1} = gen_tcp:connect(localhost, Port1, [{active, false}]),
+ {ok, S1} = gen_tcp:accept(L1),
+ ok = gen_tcp:close(L1),
+ ok = inet:setopts(Client1, [{linger, {true, 0}}]),
+ ok = gen_tcp:close(Client1),
+ receive
+ {tcp_error, S1, econnreset} ->
+ receive
+ {tcp_closed, S1} ->
+ ok;
+ Other1 ->
+ ?t:fail({unexpected2, Other1})
+ after 1 ->
+ ?t:fail({timeout, {server, no_tcp_closed}})
+ end;
+ Other2 ->
+ ?t:fail({unexpected3, Other2})
+ after 1000 ->
+ ?t:fail({timeout, {server, no_tcp_error}})
+ end.
+
+show_econnreset_active_once(Config) when is_list(Config) ->
+ %% Now test using {active, once}
+ {ok, L} = gen_tcp:listen(0,
+ [{active, false},
+ {show_econnreset, true}]),
+ {ok, Port} = inet:port(L),
+ {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]),
+ {ok, S} = gen_tcp:accept(L),
+ ok = gen_tcp:close(L),
+ ok = inet:setopts(Client, [{linger, {true, 0}}]),
+ ok = gen_tcp:close(Client),
+ ok = ?t:sleep(20),
+ ok = receive Msg -> {unexpected_msg, Msg} after 0 -> ok end,
+ ok = inet:setopts(S, [{active, once}]),
+ receive
+ {tcp_error, S, econnreset} ->
+ receive
+ {tcp_closed, S} ->
+ ok;
+ Other1 ->
+ ?t:fail({unexpected1, Other1})
+ after 1 ->
+ ?t:fail({timeout, {server, no_tcp_closed}})
+ end;
+ Other2 ->
+ ?t:fail({unexpected2, Other2})
+ after 1000 ->
+ ?t:fail({timeout, {server, no_tcp_error}})
+ end.
+
+show_econnreset_passive(Config) when is_list(Config) ->
+ %% First confirm everything works with option turned off.
+ {ok, L} = gen_tcp:listen(0, [{active, false}]),
+ {ok, Port} = inet:port(L),
+ {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]),
+ {ok, S} = gen_tcp:accept(L),
+ ok = gen_tcp:close(L),
+ ok = inet:setopts(S, [{linger, {true, 0}}]),
+ ok = gen_tcp:close(S),
+ ok = ?t:sleep(1),
+ {error, closed} = gen_tcp:recv(Client, 0),
+
+ %% Now test with option switched on.
+ {ok, L1} = gen_tcp:listen(0, [{active, false}]),
+ {ok, Port1} = inet:port(L1),
+ {ok, Client1} = gen_tcp:connect(localhost, Port1,
+ [{active, false},
+ {show_econnreset, true}]),
+ {ok, S1} = gen_tcp:accept(L1),
+ ok = gen_tcp:close(L1),
+ ok = inet:setopts(S1, [{linger, {true, 0}}]),
+ ok = gen_tcp:close(S1),
+ ok = ?t:sleep(1),
+ {error, econnreset} = gen_tcp:recv(Client1, 0).
+
%% Thanks to Luke Gorrie. Tests for a very specific problem with
%% corrupt data. The testcase will be killed by the timetrap timeout
--
cgit v1.2.3
From 0098eed847516cc6760d961421c83527816e35ae Mon Sep 17 00:00:00 2001
From: Rory Byrne
Date: Sat, 2 May 2015 20:34:24 +0100
Subject: Apply 'show_econnreset' socket option to send errors as well
Up till now all send errors have been translated into a generic
{error, closed}. This patch allows {error, econnreset} to be
returned on send errors when it is detected that the TCP peer
has sent an RST.
---
lib/kernel/doc/src/inet.xml | 5 +-
lib/kernel/test/gen_tcp_misc_SUITE.erl | 179 ++++++++++++++++++++++++++++++++-
2 files changed, 181 insertions(+), 3 deletions(-)
(limited to 'lib/kernel')
diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml
index a364f9e0c9..4dd9e0e221 100644
--- a/lib/kernel/doc/src/inet.xml
+++ b/lib/kernel/doc/src/inet.xml
@@ -1056,7 +1056,10 @@ setcap cap_sys_admin,cap_sys_ptrace,cap_dac_read_search+epi beam.smp
active mode, the controlling process will receive a
{tcp_error, Socket, econnreset} message
before the usual {tcp_closed, Socket}, as is
- the case for any other socket error.
+ the case for any other socket error. Calls to
+ gen_tcp:send/2
+ will also return {error, econnreset} when it
+ is detected that a TCP peer has sent an RST.
A connected socket returned from
gen_tcp:accept/1
will inherit the show_econnreset setting from the
diff --git a/lib/kernel/test/gen_tcp_misc_SUITE.erl b/lib/kernel/test/gen_tcp_misc_SUITE.erl
index f08f28b650..ddd9d356ee 100644
--- a/lib/kernel/test/gen_tcp_misc_SUITE.erl
+++ b/lib/kernel/test/gen_tcp_misc_SUITE.erl
@@ -32,7 +32,10 @@
otp_3924/1, otp_3924_sender/4, closed_socket/1,
shutdown_active/1, shutdown_passive/1, shutdown_pending/1,
show_econnreset_active/1, show_econnreset_active_once/1,
- show_econnreset_passive/1,
+ show_econnreset_passive/1, econnreset_after_sync_send/1,
+ econnreset_after_async_send_active/1,
+ econnreset_after_async_send_active_once/1,
+ econnreset_after_async_send_passive/1,
default_options/1, http_bad_packet/1,
busy_send/1, busy_disconnect_passive/1, busy_disconnect_active/1,
fill_sendq/1, partial_recv_and_close/1,
@@ -95,7 +98,10 @@ all() ->
accept_closed_by_other_process, otp_3924, closed_socket,
shutdown_active, shutdown_passive, shutdown_pending,
show_econnreset_active, show_econnreset_active_once,
- show_econnreset_passive,
+ show_econnreset_passive, econnreset_after_sync_send,
+ econnreset_after_async_send_active,
+ econnreset_after_async_send_active_once,
+ econnreset_after_async_send_passive,
default_options, http_bad_packet, busy_send,
busy_disconnect_passive, busy_disconnect_active,
fill_sendq, partial_recv_and_close,
@@ -1182,6 +1188,175 @@ show_econnreset_passive(Config) when is_list(Config) ->
ok = ?t:sleep(1),
{error, econnreset} = gen_tcp:recv(Client1, 0).
+econnreset_after_sync_send(Config) when is_list(Config) ->
+ %% First confirm everything works with option turned off.
+ {ok, L} = gen_tcp:listen(0, [{active, false}]),
+ {ok, Port} = inet:port(L),
+ {ok, Client} = gen_tcp:connect(localhost, Port, [{active, false}]),
+ {ok, S} = gen_tcp:accept(L),
+ ok = gen_tcp:close(L),
+ ok = inet:setopts(S, [{linger, {true, 0}}]),
+ ok = gen_tcp:close(S),
+ ok = ?t:sleep(20),
+ {error, closed} = gen_tcp:send(Client, "Whatever"),
+
+ %% Now test with option switched on.
+ {ok, L1} = gen_tcp:listen(0, [{active, false}]),
+ {ok, Port1} = inet:port(L1),
+ {ok, Client1} = gen_tcp:connect(localhost, Port1,
+ [{active, false},
+ {show_econnreset, true}]),
+ {ok, S1} = gen_tcp:accept(L1),
+ ok = gen_tcp:close(L1),
+ ok = inet:setopts(S1, [{linger, {true, 0}}]),
+ ok = gen_tcp:close(S1),
+ ok = ?t:sleep(20),
+ {error, econnreset} = gen_tcp:send(Client1, "Whatever").
+
+econnreset_after_async_send_active(Config) when is_list(Config) ->
+ {OS, _} = os:type(),
+ Payload = lists:duplicate(1024 * 1024, $.),
+
+ %% First confirm everything works with option turned off.
+ {ok, L} = gen_tcp:listen(0, [{active, false}, {recbuf, 4096}]),
+ {ok, Port} = inet:port(L),
+ {ok, Client} = gen_tcp:connect(localhost, Port, [{sndbuf, 4096}]),
+ {ok, S} = gen_tcp:accept(L),
+ ok = gen_tcp:close(L),
+ ok = gen_tcp:send(Client, Payload),
+ case erlang:port_info(Client, queue_size) of
+ {queue_size, N} when N > 0 -> ok;
+ {queue_size, 0} when OS =:= win32 -> ok;
+ {queue_size, 0} = T -> ?t:fail(T)
+ end,
+ ok = gen_tcp:send(S, "Whatever"),
+ ok = ?t:sleep(20),
+ ok = inet:setopts(S, [{linger, {true, 0}}]),
+ ok = gen_tcp:close(S),
+ ok = ?t:sleep(20),
+ receive
+ {tcp, Client, "Whatever"} ->
+ receive
+ {tcp_closed, Client} ->
+ ok;
+ Other1 ->
+ ?t:fail({unexpected1, Other1})
+ end;
+ Other2 ->
+ ?t:fail({unexpected2, Other2})
+ end,
+
+ %% Now test with option switched on.
+ {ok, L1} = gen_tcp:listen(0, [{active, false}, {recbuf, 4096}]),
+ {ok, Port1} = inet:port(L1),
+ {ok, Client1} = gen_tcp:connect(localhost, Port1,
+ [{sndbuf, 4096},
+ {show_econnreset, true}]),
+ {ok, S1} = gen_tcp:accept(L1),
+ ok = gen_tcp:close(L1),
+ ok = gen_tcp:send(Client1, Payload),
+ case erlang:port_info(Client1, queue_size) of
+ {queue_size, N1} when N1 > 0 -> ok;
+ {queue_size, 0} when OS =:= win32 -> ok;
+ {queue_size, 0} = T1 -> ?t:fail(T1)
+ end,
+ ok = gen_tcp:send(S1, "Whatever"),
+ ok = ?t:sleep(20),
+ ok = inet:setopts(S1, [{linger, {true, 0}}]),
+ ok = gen_tcp:close(S1),
+ ok = ?t:sleep(20),
+ receive
+ {tcp, Client1, "Whatever"} ->
+ receive
+ {tcp_error, Client1, econnreset} ->
+ receive
+ {tcp_closed, Client1} ->
+ ok;
+ Other3 ->
+ ?t:fail({unexpected3, Other3})
+ end;
+ Other4 ->
+ ?t:fail({unexpected4, Other4})
+ end;
+ Other5 ->
+ ?t:fail({unexpected5, Other5})
+ end.
+
+econnreset_after_async_send_active_once(Config) when is_list(Config) ->
+ {OS, _} = os:type(),
+ {ok, L} = gen_tcp:listen(0, [{active, false}, {recbuf, 4096}]),
+ {ok, Port} = inet:port(L),
+ {ok, Client} = gen_tcp:connect(localhost, Port,
+ [{active, false},
+ {sndbuf, 4096},
+ {show_econnreset, true}]),
+ {ok,S} = gen_tcp:accept(L),
+ ok = gen_tcp:close(L),
+ Payload = lists:duplicate(1024 * 1024, $.),
+ ok = gen_tcp:send(Client, Payload),
+ case erlang:port_info(Client, queue_size) of
+ {queue_size, N} when N > 0 -> ok;
+ {queue_size, 0} when OS =:= win32 -> ok;
+ {queue_size, 0} = T -> ?t:fail(T)
+ end,
+ ok = gen_tcp:send(S, "Whatever"),
+ ok = ?t:sleep(20),
+ ok = inet:setopts(S, [{linger, {true, 0}}]),
+ ok = gen_tcp:close(S),
+ ok = ?t:sleep(20),
+ ok = receive Msg -> {unexpected_msg, Msg} after 0 -> ok end,
+ ok = inet:setopts(Client, [{active, once}]),
+ receive
+ {tcp_error, Client, econnreset} ->
+ receive
+ {tcp_closed, Client} ->
+ ok;
+ Other ->
+ ?t:fail({unexpected1, Other})
+ end;
+ Other ->
+ ?t:fail({unexpected2, Other})
+ end.
+
+econnreset_after_async_send_passive(Config) when is_list(Config) ->
+ {OS, _} = os:type(),
+ Payload = lists:duplicate(1024 * 1024, $.),
+
+ %% First confirm everything works with option turned off.
+ {ok, L} = gen_tcp:listen(0, [{active, false}, {recbuf, 4096}]),
+ {ok, Port} = inet:port(L),
+ {ok, Client} = gen_tcp:connect(localhost, Port,
+ [{active, false},
+ {sndbuf, 4096}]),
+ {ok, S} = gen_tcp:accept(L),
+ ok = gen_tcp:close(L),
+ ok = inet:setopts(S, [{linger, {true, 0}}]),
+ ok = gen_tcp:send(S, "Whatever"),
+ ok = gen_tcp:send(Client, Payload),
+ case erlang:port_info(Client, queue_size) of
+ {queue_size, N} when N > 0 -> ok;
+ {queue_size, 0} when OS =:= win32 -> ok;
+ {queue_size, 0} = T -> ?t:fail(T)
+ end,
+ ok = gen_tcp:close(S),
+ ok = ?t:sleep(20),
+ {error, closed} = gen_tcp:recv(Client, 0),
+
+ %% Now test with option switched on.
+ {ok, L1} = gen_tcp:listen(0, [{active, false}, {recbuf, 4096}]),
+ {ok, Port1} = inet:port(L1),
+ {ok, Client1} = gen_tcp:connect(localhost, Port1,
+ [{active, false},
+ {sndbuf, 4096},
+ {show_econnreset, true}]),
+ {ok, S1} = gen_tcp:accept(L1),
+ ok = gen_tcp:close(L1),
+ ok = inet:setopts(S1, [{linger, {true, 0}}]),
+ ok = gen_tcp:send(S1, "Whatever"),
+ ok = gen_tcp:send(Client1, Payload),
+ ok = gen_tcp:close(S1),
+ ok = ?t:sleep(20),
+ {error, econnreset} = gen_tcp:recv(Client1, 0).
%% Thanks to Luke Gorrie. Tests for a very specific problem with
%% corrupt data. The testcase will be killed by the timetrap timeout
--
cgit v1.2.3
From eed21b4fd99c8ab71dcefd3802104186af1cb6b0 Mon Sep 17 00:00:00 2001
From: Rory Byrne
Date: Thu, 16 Apr 2015 18:01:23 +0100
Subject: Fix socket option {linger, {true, 0}} to abort TCP connections
Up until now, if {linger, {true, 0}} is set on the socket and there is
data in the port driver queue, the connection is not aborted until
the port queue is empty and close() is called on the underlying file
descriptor. This bug allows an idle TCP client to prevent a server
from terminating the connection and freeing resources. This patch
fixes the problem by discarding the port queue if the socket is closed
when {linger, {true, 0}} is set.
---
lib/kernel/test/gen_tcp_misc_SUITE.erl | 37 ++++++++++++++++++++++++++++++++--
1 file changed, 35 insertions(+), 2 deletions(-)
(limited to 'lib/kernel')
diff --git a/lib/kernel/test/gen_tcp_misc_SUITE.erl b/lib/kernel/test/gen_tcp_misc_SUITE.erl
index ddd9d356ee..83bfd1f69d 100644
--- a/lib/kernel/test/gen_tcp_misc_SUITE.erl
+++ b/lib/kernel/test/gen_tcp_misc_SUITE.erl
@@ -35,7 +35,7 @@
show_econnreset_passive/1, econnreset_after_sync_send/1,
econnreset_after_async_send_active/1,
econnreset_after_async_send_active_once/1,
- econnreset_after_async_send_passive/1,
+ econnreset_after_async_send_passive/1, linger_zero/1,
default_options/1, http_bad_packet/1,
busy_send/1, busy_disconnect_passive/1, busy_disconnect_active/1,
fill_sendq/1, partial_recv_and_close/1,
@@ -101,7 +101,7 @@ all() ->
show_econnreset_passive, econnreset_after_sync_send,
econnreset_after_async_send_active,
econnreset_after_async_send_active_once,
- econnreset_after_async_send_passive,
+ econnreset_after_async_send_passive, linger_zero,
default_options, http_bad_packet, busy_send,
busy_disconnect_passive, busy_disconnect_active,
fill_sendq, partial_recv_and_close,
@@ -1358,6 +1358,39 @@ econnreset_after_async_send_passive(Config) when is_list(Config) ->
ok = ?t:sleep(20),
{error, econnreset} = gen_tcp:recv(Client1, 0).
+%%
+%% Test {linger {true, 0}} aborts a connection
+%%
+
+linger_zero(Config) when is_list(Config) ->
+ %% All the econnreset tests will prove that {linger, {true, 0}} aborts
+ %% a connection when the driver queue is empty. We will test here
+ %% that it also works when the driver queue is not empty.
+ {OS, _} = os:type(),
+ {ok, L} = gen_tcp:listen(0, [{active, false},
+ {recbuf, 4096},
+ {show_econnreset, true}]),
+ {ok, Port} = inet:port(L),
+ {ok, Client} = gen_tcp:connect(localhost, Port,
+ [{active, false},
+ {sndbuf, 4096}]),
+ {ok, S} = gen_tcp:accept(L),
+ ok = gen_tcp:close(L),
+ PayloadSize = 1024 * 1024,
+ Payload = lists:duplicate(PayloadSize, $.),
+ ok = gen_tcp:send(Client, Payload),
+ case erlang:port_info(Client, queue_size) of
+ {queue_size, N} when N > 0 -> ok;
+ {queue_size, 0} when OS =:= win32 -> ok;
+ {queue_size, 0} = T -> ?t:fail(T)
+ end,
+ ok = inet:setopts(Client, [{linger, {true, 0}}]),
+ ok = gen_tcp:close(Client),
+ ok = ?t:sleep(1),
+ undefined = erlang:port_info(Client, connected),
+ {error, econnreset} = gen_tcp:recv(S, PayloadSize).
+
+
%% Thanks to Luke Gorrie. Tests for a very specific problem with
%% corrupt data. The testcase will be killed by the timetrap timeout
%% if the bug is present.
--
cgit v1.2.3