diff --git a/CMakeLists.txt b/CMakeLists.txt index ee06d4df..3f712a80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -568,6 +568,7 @@ if(LIBUV_BUILD_TESTS) test/test-tcp-bind-error.c test/test-tcp-bind6-error.c test/test-tcp-close-accept.c + test/test-tcp-close-after-read-timeout.c test/test-tcp-close-while-connecting.c test/test-tcp-close.c test/test-tcp-close-reset.c diff --git a/Makefile.am b/Makefile.am index a377759f..a7ee5d46 100644 --- a/Makefile.am +++ b/Makefile.am @@ -258,6 +258,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-tcp-bind6-error.c \ test/test-tcp-close-accept.c \ test/test-tcp-close-while-connecting.c \ + test/test-tcp-close-after-read-timeout.c \ test/test-tcp-close.c \ test/test-tcp-close-reset.c \ test/test-tcp-create-socket-early.c \ diff --git a/src/win/tcp.c b/src/win/tcp.c index 1d9085c9..08083df3 100644 --- a/src/win/tcp.c +++ b/src/win/tcp.c @@ -1411,7 +1411,7 @@ static void uv__tcp_try_cancel_reqs(uv_tcp_t* tcp) { int writing; socket = tcp->socket; - reading = tcp->flags & UV_HANDLE_READING; + reading = tcp->flags & UV_HANDLE_READ_PENDING; writing = tcp->stream.conn.write_reqs_pending > 0; if (!reading && !writing) return; @@ -1458,10 +1458,10 @@ static void uv__tcp_try_cancel_reqs(uv_tcp_t* tcp) { void uv__tcp_close(uv_loop_t* loop, uv_tcp_t* tcp) { if (tcp->flags & UV_HANDLE_CONNECTION) { - uv__tcp_try_cancel_reqs(tcp); if (tcp->flags & UV_HANDLE_READING) { uv_read_stop((uv_stream_t*) tcp); } + uv__tcp_try_cancel_reqs(tcp); } else { if (tcp->tcp.serv.accept_reqs != NULL) { /* First close the incoming sockets to cancel the accept operations before diff --git a/test/test-list.h b/test/test-list.h index 26c14b06..833047e8 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -129,6 +129,7 @@ TEST_DECLARE (tcp_connect_timeout) TEST_DECLARE (tcp_local_connect_timeout) TEST_DECLARE (tcp6_local_connect_timeout) TEST_DECLARE (tcp_close_while_connecting) +TEST_DECLARE (tcp_close_after_read_timeout) TEST_DECLARE (tcp_close) TEST_DECLARE (tcp_close_reset_accepted) TEST_DECLARE (tcp_close_reset_accepted_after_shutdown) @@ -704,6 +705,7 @@ TASK_LIST_START TEST_ENTRY (tcp_local_connect_timeout) TEST_ENTRY (tcp6_local_connect_timeout) TEST_ENTRY (tcp_close_while_connecting) + TEST_ENTRY (tcp_close_after_read_timeout) TEST_ENTRY (tcp_close) TEST_ENTRY (tcp_close_reset_accepted) TEST_ENTRY (tcp_close_reset_accepted_after_shutdown) diff --git a/test/test-tcp-close-after-read-timeout.c b/test/test-tcp-close-after-read-timeout.c new file mode 100644 index 00000000..493492db --- /dev/null +++ b/test/test-tcp-close-after-read-timeout.c @@ -0,0 +1,183 @@ +/* Copyright libuv project and contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "task.h" + +static uv_tcp_t client; +static uv_tcp_t connection; +static uv_connect_t connect_req; +static uv_timer_t timer; + +static int read_cb_called; +static int on_close_called; + +static void on_connection(uv_stream_t* server, int status); + +static void on_client_connect(uv_connect_t* req, int status); +static void on_client_alloc(uv_handle_t* handle, + size_t suggested_size, + uv_buf_t* buf); +static void on_client_read(uv_stream_t* stream, + ssize_t nread, + const uv_buf_t* buf); +static void on_client_timeout(uv_timer_t* handle); + +static void on_close(uv_handle_t* handle); + + +static void on_client_connect(uv_connect_t* conn_req, int status) { + int r; + + r = uv_read_start((uv_stream_t*) &client, on_client_alloc, on_client_read); + ASSERT_EQ(r, 0); + + r = uv_timer_start(&timer, on_client_timeout, 1000, 0); + ASSERT_EQ(r, 0); +} + + +static void on_client_alloc(uv_handle_t* handle, + size_t suggested_size, + uv_buf_t* buf) { + static char slab[8]; + buf->base = slab; + buf->len = sizeof(slab); +} + + +static void on_client_read(uv_stream_t* stream, ssize_t nread, + const uv_buf_t* buf) { + ASSERT_LT(nread, 0); + read_cb_called++; +} + + +static void on_client_timeout(uv_timer_t* handle) { + ASSERT_EQ(handle, &timer); + ASSERT_EQ(read_cb_called, 0); + uv_read_stop((uv_stream_t*) &client); + uv_close((uv_handle_t*) &client, on_close); + uv_close((uv_handle_t*) &timer, on_close); +} + + +static void on_connection_alloc(uv_handle_t* handle, + size_t suggested_size, + uv_buf_t* buf) { + static char slab[8]; + buf->base = slab; + buf->len = sizeof(slab); +} + + +static void on_connection_read(uv_stream_t* stream, + ssize_t nread, + const uv_buf_t* buf) { + ASSERT_EQ(nread, UV_EOF); + read_cb_called++; + uv_close((uv_handle_t*) stream, on_close); +} + + +static void on_connection(uv_stream_t* server, int status) { + int r; + + ASSERT_EQ(status, 0); + ASSERT_EQ(uv_accept(server, (uv_stream_t*) &connection), 0); + + r = uv_read_start((uv_stream_t*) &connection, + on_connection_alloc, + on_connection_read); + ASSERT_EQ(r, 0); +} + + +static void on_close(uv_handle_t* handle) { + ASSERT(handle == (uv_handle_t*) &client || + handle == (uv_handle_t*) &connection || + handle == (uv_handle_t*) &timer); + on_close_called++; +} + + +static void start_server(uv_loop_t* loop, uv_tcp_t* handle) { + struct sockaddr_in addr; + int r; + + ASSERT_EQ(uv_ip4_addr("127.0.0.1", TEST_PORT, &addr), 0); + + r = uv_tcp_init(loop, handle); + ASSERT_EQ(r, 0); + + r = uv_tcp_bind(handle, (const struct sockaddr*) &addr, 0); + ASSERT_EQ(r, 0); + + r = uv_listen((uv_stream_t*) handle, 128, on_connection); + ASSERT_EQ(r, 0); + + uv_unref((uv_handle_t*) handle); +} + + +/* Check that pending write requests have their callbacks + * invoked when the handle is closed. + */ +TEST_IMPL(tcp_close_after_read_timeout) { + struct sockaddr_in addr; + uv_tcp_t tcp_server; + uv_loop_t* loop; + int r; + + ASSERT_EQ(uv_ip4_addr("127.0.0.1", TEST_PORT, &addr), 0); + + loop = uv_default_loop(); + + /* We can't use the echo server, it doesn't handle ECONNRESET. */ + start_server(loop, &tcp_server); + + r = uv_tcp_init(loop, &client); + ASSERT_EQ(r, 0); + + r = uv_tcp_connect(&connect_req, + &client, + (const struct sockaddr*) &addr, + on_client_connect); + ASSERT_EQ(r, 0); + + r = uv_tcp_init(loop, &connection); + ASSERT_EQ(r, 0); + + r = uv_timer_init(loop, &timer); + ASSERT_EQ(r, 0); + + ASSERT_EQ(read_cb_called, 0); + ASSERT_EQ(on_close_called, 0); + + r = uv_run(loop, UV_RUN_DEFAULT); + ASSERT_EQ(r, 0); + + ASSERT_EQ(read_cb_called, 1); + ASSERT_EQ(on_close_called, 3); + + MAKE_VALGRIND_HAPPY(); + return 0; +}