From: Martyn Forryan <winehq-gitlab@forryan.co.uk> Linux withholds POLLOUT from stream sockets until the send queue reaches its low-water mark, while sendmsg() accepts data whenever sk_wmem_queued is below sk_sndbuf. This leaves sockets reported as not writable even though a send would succeed. Use SO_MEMINFO to report writability from the kernel's own send-accept condition. Apply the same condition in the send_socket blocking gate; otherwise Wine parks a blocking send on raw POLLOUT even when the kernel would accept it. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=59893 --- dlls/ws2_32/tests/sock.c | 2 +- server/sock.c | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index 44a6977f66c..3009d529e1b 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -7277,7 +7277,7 @@ static void test_send_writability(void) ret = setsockopt(server, SOL_SOCKET, SO_SNDBUF, (char *)&value, sizeof(value)); ok(!ret, "got %d, error %u\n", ret, WSAGetLastError()); writable = socket_select_writable(server); - todo_wine ok(writable == 1, "got writable %d\n", writable); + ok(writable == 1, "got writable %d\n", writable); ret = send(server, buffer, sizeof(buffer), 0); send_error = ret == SOCKET_ERROR ? WSAGetLastError() : 0; ok(ret == sizeof(buffer), "got %d, error %u\n", ret, send_error); diff --git a/server/sock.c b/server/sock.c index 0716b3f71f4..403a2d9aff1 100644 --- a/server/sock.c +++ b/server/sock.c @@ -62,6 +62,9 @@ #ifdef HAVE_LINUX_RTNETLINK_H # include <linux/rtnetlink.h> #endif +#ifdef SO_MEMINFO +# include <linux/sock_diag.h> +#endif #ifdef HAVE_NETIPX_IPX_H # include <netipx/ipx.h> @@ -3577,6 +3580,29 @@ static void handle_exclusive_poll(struct poll_req *req) } } +static int sock_stream_send_ready( struct sock *sock ) +{ +#ifdef SO_MEMINFO + unsigned int meminfo[SK_MEMINFO_VARS]; + socklen_t len = sizeof(meminfo); + int unix_fd; + + if (sock->type != WS_SOCK_STREAM || sock->state != SOCK_CONNECTED || sock->wr_shutdown) + return 0; + + if ((unix_fd = get_unix_fd( sock->fd )) < 0) + return 0; + + if (getsockopt( unix_fd, SOL_SOCKET, SO_MEMINFO, meminfo, &len )) return 0; + + return len >= (SK_MEMINFO_WMEM_QUEUED + 1) * sizeof(*meminfo) && + meminfo[SK_MEMINFO_WMEM_QUEUED] < meminfo[SK_MEMINFO_SNDBUF]; +#else + (void)sock; + return 0; +#endif +} + static void poll_socket( struct sock *poll_sock, struct async *async, int exclusive, timeout_t timeout, unsigned int count, const struct afd_poll_socket_64 *sockets ) { @@ -3638,7 +3664,17 @@ static void poll_socket( struct sock *poll_sock, struct async *async, int exclus pollfd.fd = get_unix_fd( sock->fd ); pollfd.events = poll_flags_from_afd( sock, mask ); if (pollfd.events >= 0 && poll( &pollfd, 1, 0 ) >= 0) + { + if ((mask & AFD_POLL_WRITE) && + !(pollfd.revents & (POLLOUT | POLLERR | POLLHUP)) && + sock->type == WS_SOCK_STREAM && sock->state == SOCK_CONNECTED && + !sock->wr_shutdown) + { + if (sock_stream_send_ready( sock )) + pollfd.revents |= POLLOUT; + } sock_poll_event( sock->fd, pollfd.revents ); + } /* FIXME: do other error conditions deserve a similar treatment? */ if (sock->state != SOCK_CONNECTING && sock->errors[AFD_POLL_BIT_CONNECT_ERR] && (mask & AFD_POLL_CONNECT_ERR)) @@ -4052,7 +4088,8 @@ DECL_HANDLER(send_socket) * asyncs will not consume all available space; if there's no space * available, the current request won't be immediately satiable. */ - if ((!force_async && sock->nonblocking) || check_fd_events( sock->fd, POLLOUT )) + if ((!force_async && sock->nonblocking) || check_fd_events( sock->fd, POLLOUT ) || + sock_stream_send_ready( sock )) { /* Give the client opportunity to complete synchronously. * If it turns out that the I/O request is not actually immediately satiable, -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11272