From: Martyn Forryan <winehq-gitlab@forryan.co.uk> Windows select() reports a connected stream socket writable whenever send() would still accept data. Wine follows the host poll(); on Linux POLLOUT is only set once the send queue drains below ~2/3 of SO_SNDBUF. An application that performs a select() writability check before waiting on FD_WRITE - for example libcurl's multi event loop, and therefore anything built on it - then sees the socket as not writable while its sends keep succeeding, and waits out its full poll timeout (typically 1s) between bursts, throttling single-stream uploads to ~140 KB/s. Report a connected stream socket writable in poll_socket() when its send buffer still has room (TIOCOUTQ < SO_SNDBUF), matching Windows. Guard with #ifdef TIOCOUTQ so behaviour is unchanged where it is unavailable. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=59893 --- dlls/ws2_32/tests/sock.c | 2 +- server/sock.c | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index a4424d6d51d..d31d6d1e5eb 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -6412,7 +6412,7 @@ static void test_send_writability(void) } ok(filled, "send buffer was not filled after %u bytes\n", total); - todo_wine ok(!violations, "select/send writability invariant was violated %u times\n", violations); + ok(!violations, "select/send writability invariant was violated %u times\n", violations); closesocket(client); closesocket(server); diff --git a/server/sock.c b/server/sock.c index fdc26bc2276..c4c3ed63edf 100644 --- a/server/sock.c +++ b/server/sock.c @@ -2,6 +2,7 @@ * Server-side socket management * * Copyright (C) 1999 Marcus Meissner, Ove Kåven + * Copyright (C) 2026 Martyn Forryan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -3652,7 +3653,23 @@ 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) + { +#ifdef TIOCOUTQ + int outq = 0, sndbuf = 0; + socklen_t len = sizeof(sndbuf); + + /* Linux withholds POLLOUT until the send queue has drained well below + * SO_SNDBUF, while Windows reports a stream socket writable whenever + * send() can still accept data. If there is any send-buffer space + * left, report writability here to match Windows semantics. */ + if ((mask & AFD_POLL_WRITE) && !(pollfd.revents & (POLLOUT | POLLERR | POLLHUP)) && + sock->type == WS_SOCK_STREAM && sock->state == SOCK_CONNECTED && !sock->wr_shutdown && + !ioctl( pollfd.fd, TIOCOUTQ, &outq ) && + !getsockopt( pollfd.fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, &len ) && outq < sndbuf) + pollfd.revents |= POLLOUT; +#endif 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)) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11272