Skip to content

Commit 74b8380

Browse files
committed
win,tcp: make uv_close work like unix
This is an attempt to fix some resource management issues on Windows. Win32 sockets have an issue where it sends an RST packet if there is an outstanding overlapped calls. We can avoid that by being certain to explicitly cancel our read and write requests first. This also removes some conditional cleanup code, since we might as well clean it up eagerly (like unix). Otherwise, it looks to me like these might cause the accept callbacks to be run after the endgame had freed the memory for them. The comment here seems mixed up between send and recv buffers. The default behavior on calling `closesocket` is already to do a graceful shutdown (see https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-closesocket with default l_onoff=zero) if it is the last open handle. The expected behavior if there are pending reads in flight is to send an RST packet, notifying the client that the server connection was destroyed before acknowledging the EOF. Refs: libuv#3035 Refs: nodejs/node#35946 Refs: nodejs/node#35904 Fixes: libuv#3034 PR-URL: libuv#3036
1 parent 4ddc292 commit 74b8380

2 files changed

Lines changed: 25 additions & 65 deletions

File tree

src/win/tcp.c

Lines changed: 24 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -237,11 +237,7 @@ void uv_tcp_endgame(uv_loop_t* loop, uv_tcp_t* handle) {
237237
handle->reqs_pending == 0) {
238238
assert(!(handle->flags & UV_HANDLE_CLOSED));
239239

240-
if (!(handle->flags & UV_HANDLE_TCP_SOCKET_CLOSED)) {
241-
closesocket(handle->socket);
242-
handle->socket = INVALID_SOCKET;
243-
handle->flags |= UV_HANDLE_TCP_SOCKET_CLOSED;
244-
}
240+
assert(handle->flags & UV_HANDLE_TCP_SOCKET_CLOSED);
245241

246242
if (!(handle->flags & UV_HANDLE_CONNECTION) && handle->tcp.serv.accept_reqs) {
247243
if (handle->flags & UV_HANDLE_EMULATE_IOCP) {
@@ -769,7 +765,7 @@ static int uv__is_loopback(const struct sockaddr_storage* storage) {
769765
}
770766

771767
// Check if Windows version is 10.0.16299 or later
772-
static int uv__is_fast_loopback_fail_supported() {
768+
static int uv__is_fast_loopback_fail_supported(void) {
773769
OSVERSIONINFOW os_info;
774770
if (!pRtlGetVersion)
775771
return 0;
@@ -1402,13 +1398,16 @@ int uv_tcp_simultaneous_accepts(uv_tcp_t* handle, int enable) {
14021398
}
14031399

14041400

1405-
static int uv_tcp_try_cancel_io(uv_tcp_t* tcp) {
1406-
SOCKET socket = tcp->socket;
1401+
static void uv_tcp_try_cancel_read(uv_tcp_t* tcp) {
1402+
SOCKET socket;
14071403
int non_ifs_lsp;
14081404

1405+
CancelIoEx((HANDLE) tcp->socket, &tcp->read_req.u.io.overlapped);
1406+
14091407
/* Check if we have any non-IFS LSPs stacked on top of TCP */
14101408
non_ifs_lsp = (tcp->flags & UV_HANDLE_IPV6) ? uv_tcp_non_ifs_lsp_ipv6 :
14111409
uv_tcp_non_ifs_lsp_ipv4;
1410+
socket = tcp->socket;
14121411

14131412
/* If there are non-ifs LSPs then try to obtain a base handle for the socket.
14141413
* This will always fail on Windows XP/3k. */
@@ -1424,83 +1423,44 @@ static int uv_tcp_try_cancel_io(uv_tcp_t* tcp) {
14241423
NULL,
14251424
NULL) != 0) {
14261425
/* Failed. We can't do CancelIo. */
1427-
return -1;
1426+
return;
14281427
}
14291428
}
14301429

14311430
assert(socket != 0 && socket != INVALID_SOCKET);
14321431

1433-
if (!CancelIo((HANDLE) socket)) {
1434-
return GetLastError();
1432+
if (socket != tcp->socket) {
1433+
CancelIoEx((HANDLE) socket, &tcp->read_req.u.io.overlapped);
14351434
}
1436-
1437-
/* It worked. */
1438-
return 0;
14391435
}
14401436

14411437

14421438
void uv_tcp_close(uv_loop_t* loop, uv_tcp_t* tcp) {
1443-
int close_socket = 1;
1444-
1445-
if (tcp->flags & UV_HANDLE_READ_PENDING) {
1446-
/* In order for winsock to do a graceful close there must not be any any
1447-
* pending reads, or the socket must be shut down for writing */
1448-
if (!(tcp->flags & UV_HANDLE_SHARED_TCP_SOCKET)) {
1449-
/* Just do shutdown on non-shared sockets, which ensures graceful close. */
1450-
shutdown(tcp->socket, SD_SEND);
1451-
1452-
} else if (uv_tcp_try_cancel_io(tcp) == 0) {
1453-
/* In case of a shared socket, we try to cancel all outstanding I/O,. If
1454-
* that works, don't close the socket yet - wait for the read req to
1455-
* return and close the socket in uv_tcp_endgame. */
1456-
close_socket = 0;
1457-
1458-
} else {
1459-
/* When cancelling isn't possible - which could happen when an LSP is
1460-
* present on an old Windows version, we will have to close the socket
1461-
* with a read pending. That is not nice because trailing sent bytes may
1462-
* not make it to the other side. */
1463-
}
1464-
1465-
} else if ((tcp->flags & UV_HANDLE_SHARED_TCP_SOCKET) &&
1466-
tcp->tcp.serv.accept_reqs != NULL) {
1467-
/* Under normal circumstances closesocket() will ensure that all pending
1468-
* accept reqs are canceled. However, when the socket is shared the
1469-
* presence of another reference to the socket in another process will keep
1470-
* the accept reqs going, so we have to ensure that these are canceled. */
1471-
if (uv_tcp_try_cancel_io(tcp) != 0) {
1472-
/* When cancellation is not possible, there is another option: we can
1473-
* close the incoming sockets, which will also cancel the accept
1474-
* operations. However this is not cool because we might inadvertently
1475-
* close a socket that just accepted a new connection, which will cause
1476-
* the connection to be aborted. */
1477-
unsigned int i;
1478-
for (i = 0; i < uv_simultaneous_server_accepts; i++) {
1479-
uv_tcp_accept_t* req = &tcp->tcp.serv.accept_reqs[i];
1480-
if (req->accept_socket != INVALID_SOCKET &&
1481-
!HasOverlappedIoCompleted(&req->u.io.overlapped)) {
1482-
closesocket(req->accept_socket);
1483-
req->accept_socket = INVALID_SOCKET;
1484-
}
1439+
if (tcp->tcp.serv.accept_reqs != NULL) {
1440+
/* First close the incoming sockets to cancel the accept operations before
1441+
* we free their resources. */
1442+
unsigned int i;
1443+
for (i = 0; i < uv_simultaneous_server_accepts; i++) {
1444+
uv_tcp_accept_t* req = &tcp->tcp.serv.accept_reqs[i];
1445+
if (req->accept_socket != INVALID_SOCKET) {
1446+
closesocket(req->accept_socket);
1447+
req->accept_socket = INVALID_SOCKET;
14851448
}
14861449
}
14871450
}
14881451

14891452
if (tcp->flags & UV_HANDLE_READING) {
1490-
tcp->flags &= ~UV_HANDLE_READING;
1491-
DECREASE_ACTIVE_COUNT(loop, tcp);
1453+
uv_tcp_try_cancel_read(tcp);
1454+
uv_read_stop((uv_stream_t*) tcp);
14921455
}
1493-
14941456
if (tcp->flags & UV_HANDLE_LISTENING) {
14951457
tcp->flags &= ~UV_HANDLE_LISTENING;
14961458
DECREASE_ACTIVE_COUNT(loop, tcp);
14971459
}
14981460

1499-
if (close_socket) {
1500-
closesocket(tcp->socket);
1501-
tcp->socket = INVALID_SOCKET;
1502-
tcp->flags |= UV_HANDLE_TCP_SOCKET_CLOSED;
1503-
}
1461+
closesocket(tcp->socket);
1462+
tcp->socket = INVALID_SOCKET;
1463+
tcp->flags |= UV_HANDLE_TCP_SOCKET_CLOSED;
15041464

15051465
tcp->flags &= ~(UV_HANDLE_READABLE | UV_HANDLE_WRITABLE);
15061466
uv__handle_closing(tcp);

test/test-ipc.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ static void on_read(uv_stream_t* handle,
203203
/* Make sure that the expected data is correctly multiplexed. */
204204
ASSERT_MEM_EQ("hello\n", buf->base, nread);
205205

206-
outbuf = uv_buf_init("world\n", 6);
206+
outbuf = uv_buf_init("foobar\n", 7);
207207
r = uv_write(&write_req, (uv_stream_t*)pipe, &outbuf, 1, NULL);
208208
ASSERT_EQ(r, 0);
209209

0 commit comments

Comments
 (0)