diff --git a/src/unix/tcp.c b/src/unix/tcp.c index f1040e98..73fc657a 100644 --- a/src/unix/tcp.c +++ b/src/unix/tcp.c @@ -321,8 +321,16 @@ int uv_tcp_close_reset(uv_tcp_t* handle, uv_close_cb close_cb) { return UV_EINVAL; fd = uv__stream_fd(handle); - if (0 != setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l))) - return UV__ERR(errno); + if (0 != setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l))) { + if (errno == EINVAL) { + /* Open Group Specifications Issue 7, 2018 edition states that + * EINVAL may mean the socket has been shut down already. + * Behavior observed on Solaris, illumos and macOS. */ + errno = 0; + } else { + return UV__ERR(errno); + } + } uv_close((uv_handle_t*) handle, close_cb); return 0; diff --git a/test/test-list.h b/test/test-list.h index e74c1009..b19c10c7 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -133,6 +133,7 @@ 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) +TEST_DECLARE (tcp_close_reset_accepted_after_socket_shutdown) TEST_DECLARE (tcp_close_reset_client) TEST_DECLARE (tcp_close_reset_client_after_shutdown) TEST_DECLARE (tcp_create_early) @@ -710,6 +711,7 @@ TASK_LIST_START TEST_ENTRY (tcp_close) TEST_ENTRY (tcp_close_reset_accepted) TEST_ENTRY (tcp_close_reset_accepted_after_shutdown) + TEST_ENTRY (tcp_close_reset_accepted_after_socket_shutdown) TEST_ENTRY (tcp_close_reset_client) TEST_ENTRY (tcp_close_reset_client_after_shutdown) TEST_ENTRY (tcp_create_early) diff --git a/test/test-tcp-close-reset.c b/test/test-tcp-close-reset.c index 7ca55c4c..66dfc82e 100644 --- a/test/test-tcp-close-reset.c +++ b/test/test-tcp-close-reset.c @@ -25,6 +25,12 @@ #include #include /* memset */ +#ifdef _WIN32 +# define INVALID_FD (INVALID_HANDLE_VALUE) +#else +# define INVALID_FD (-1) +#endif + static uv_loop_t* loop; static uv_tcp_t tcp_server; static uv_tcp_t tcp_client; @@ -62,9 +68,22 @@ static void do_write(uv_tcp_t* handle) { static void do_close(uv_tcp_t* handle) { + uv_os_fd_t fd; + int r; + if (shutdown_before_close == 1) { ASSERT(0 == uv_shutdown(&shutdown_req, (uv_stream_t*) handle, shutdown_cb)); ASSERT(UV_EINVAL == uv_tcp_close_reset(handle, close_cb)); + } else if (shutdown_before_close == 2) { + r = uv_fileno((const uv_handle_t*) handle, &fd); + ASSERT_EQ(r, 0); + ASSERT_NE(fd, INVALID_FD); +#ifdef _WIN32 + ASSERT_EQ(0, shutdown(fd, SD_BOTH)); +#else + ASSERT_EQ(0, shutdown(fd, SHUT_RDWR)); +#endif + ASSERT_EQ(0, uv_tcp_close_reset(handle, close_cb)); } else { ASSERT(0 == uv_tcp_close_reset(handle, close_cb)); ASSERT(UV_ENOTCONN == uv_shutdown(&shutdown_req, (uv_stream_t*) handle, shutdown_cb)); @@ -288,3 +307,30 @@ TEST_IMPL(tcp_close_reset_accepted_after_shutdown) { MAKE_VALGRIND_HAPPY(); return 0; } + +TEST_IMPL(tcp_close_reset_accepted_after_socket_shutdown) { + int r; + + loop = uv_default_loop(); + + start_server(loop, &tcp_server); + + client_close = 0; + shutdown_before_close = 2; + + do_connect(loop, &tcp_client); + + ASSERT_EQ(write_cb_called, 0); + ASSERT_EQ(close_cb_called, 0); + ASSERT_EQ(shutdown_cb_called, 0); + + r = uv_run(loop, UV_RUN_DEFAULT); + ASSERT_EQ(r, 0); + + ASSERT_EQ(write_cb_called, 4); + ASSERT_EQ(close_cb_called, 1); + ASSERT_EQ(shutdown_cb_called, 0); + + MAKE_VALGRIND_HAPPY(); + return 0; +}