winhttp: Allow synchronous nonblocking send in WinHttpWebSocketSend() when possible.

Signed-off-by: Paul Gofman <pgofman@codeweavers.com>
Signed-off-by: Hans Leidekker <hans@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Paul Gofman 2022-01-24 12:43:14 +03:00 committed by Alexandre Julliard
parent ad5d011f58
commit 9ef1e44914
4 changed files with 111 additions and 42 deletions

View File

@ -32,15 +32,23 @@
WINE_DEFAULT_DEBUG_CHANNEL(winhttp);
static int sock_send(int fd, const void *msg, size_t len, int flags)
static int sock_send(int fd, const void *msg, size_t len, WSAOVERLAPPED *ovr)
{
int ret;
do
WSABUF wsabuf;
DWORD size;
DWORD err;
wsabuf.len = len;
wsabuf.buf = (void *)msg;
if (!WSASend( (SOCKET)fd, &wsabuf, 1, &size, 0, ovr, NULL ))
{
if ((ret = send(fd, msg, len, flags)) == -1) WARN("send error %u\n", WSAGetLastError());
assert( size == len );
return size;
}
while(ret == -1 && WSAGetLastError() == WSAEINTR);
return ret;
err = WSAGetLastError();
if (!(ovr && err == WSA_IO_PENDING)) WARN( "send error %u\n", err );
return -1;
}
static int sock_recv(int fd, void *msg, size_t len, int flags)
@ -190,7 +198,7 @@ DWORD netconn_create( struct hostdata *host, const struct sockaddr_storage *sock
if (!(conn = calloc( 1, sizeof(*conn) ))) return ERROR_OUTOFMEMORY;
conn->host = host;
conn->sockaddr = *sockaddr;
if ((conn->socket = socket( sockaddr->ss_family, SOCK_STREAM, 0 )) == -1)
if ((conn->socket = WSASocketW( sockaddr->ss_family, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED )) == -1)
{
ret = WSAGetLastError();
WARN("unable to create socket (%u)\n", ret);
@ -290,7 +298,7 @@ DWORD netconn_secure_connect( struct netconn *conn, WCHAR *hostname, DWORD secur
TRACE("sending %u bytes\n", out_buf.cbBuffer);
size = sock_send(conn->socket, out_buf.pvBuffer, out_buf.cbBuffer, 0);
size = sock_send(conn->socket, out_buf.pvBuffer, out_buf.cbBuffer, NULL);
if(size != out_buf.cbBuffer) {
ERR("send failed\n");
res = ERROR_WINHTTP_SECURE_CHANNEL_ERROR;
@ -398,7 +406,7 @@ DWORD netconn_secure_connect( struct netconn *conn, WCHAR *hostname, DWORD secur
return ERROR_SUCCESS;
}
static DWORD send_ssl_chunk( struct netconn *conn, const void *msg, size_t size )
static DWORD send_ssl_chunk( struct netconn *conn, const void *msg, size_t size, WSAOVERLAPPED *ovr )
{
SecBuffer bufs[4] = {
{conn->ssl_sizes.cbHeader, SECBUFFER_STREAM_HEADER, conn->ssl_write_buf},
@ -416,7 +424,8 @@ static DWORD send_ssl_chunk( struct netconn *conn, const void *msg, size_t size
return res;
}
if (sock_send( conn->socket, conn->ssl_write_buf, bufs[0].cbBuffer + bufs[1].cbBuffer + bufs[2].cbBuffer, 0 ) < 1)
if (sock_send( conn->socket, conn->ssl_write_buf,
bufs[0].cbBuffer + bufs[1].cbBuffer + bufs[2].cbBuffer, ovr ) < 1)
{
WARN("send failed\n");
return WSAGetLastError();
@ -425,7 +434,7 @@ static DWORD send_ssl_chunk( struct netconn *conn, const void *msg, size_t size
return ERROR_SUCCESS;
}
DWORD netconn_send( struct netconn *conn, const void *msg, size_t len, int *sent )
DWORD netconn_send( struct netconn *conn, const void *msg, size_t len, int *sent, WSAOVERLAPPED *ovr )
{
if (conn->secure)
{
@ -433,11 +442,13 @@ DWORD netconn_send( struct netconn *conn, const void *msg, size_t len, int *sent
size_t chunk_size;
DWORD res;
if (ovr && len > conn->ssl_sizes.cbMaximumMessage) return WSAEWOULDBLOCK;
*sent = 0;
while (len)
{
chunk_size = min( len, conn->ssl_sizes.cbMaximumMessage );
if ((res = send_ssl_chunk( conn, ptr, chunk_size )))
if ((res = send_ssl_chunk( conn, ptr, chunk_size, ovr )))
return res;
*sent += chunk_size;
@ -448,7 +459,7 @@ DWORD netconn_send( struct netconn *conn, const void *msg, size_t len, int *sent
return ERROR_SUCCESS;
}
if ((*sent = sock_send( conn->socket, msg, len, 0 )) < 0) return WSAGetLastError();
if ((*sent = sock_send( conn->socket, msg, len, ovr )) < 0) return WSAGetLastError();
return ERROR_SUCCESS;
}

View File

@ -1286,7 +1286,7 @@ static DWORD secure_proxy_connect( struct request *request )
if (!strA) return ERROR_OUTOFMEMORY;
len = strlen( strA );
ret = netconn_send( request->netconn, strA, len, &bytes_sent );
ret = netconn_send( request->netconn, strA, len, &bytes_sent, NULL );
free( strA );
if (!ret) ret = read_reply( request );
@ -2138,13 +2138,13 @@ static DWORD send_request( struct request *request, const WCHAR *headers, DWORD
send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_SENDING_REQUEST, NULL, 0 );
ret = netconn_send( request->netconn, wire_req, len, &bytes_sent );
ret = netconn_send( request->netconn, wire_req, len, &bytes_sent, NULL );
free( wire_req );
if (ret) goto end;
if (optional_len)
{
if ((ret = netconn_send( request->netconn, optional, optional_len, &bytes_sent ))) goto end;
if ((ret = netconn_send( request->netconn, optional, optional_len, &bytes_sent, NULL ))) goto end;
request->optional = optional;
request->optional_len = optional_len;
len += optional_len;
@ -2972,7 +2972,7 @@ static DWORD write_data( struct request *request, const void *buffer, DWORD to_w
DWORD ret;
int num_bytes;
ret = netconn_send( request->netconn, buffer, to_write, &num_bytes );
ret = netconn_send( request->netconn, buffer, to_write, &num_bytes, NULL );
if (async)
{
@ -3127,11 +3127,11 @@ HINTERNET WINAPI WinHttpWebSocketCompleteUpgrade( HINTERNET hrequest, DWORD_PTR
return hsocket;
}
static DWORD send_bytes( struct socket *socket, char *bytes, int len )
static DWORD send_bytes( struct socket *socket, char *bytes, int len, WSAOVERLAPPED *ovr )
{
int count;
DWORD err;
if ((err = netconn_send( socket->request->netconn, bytes, len, &count ))) return err;
if ((err = netconn_send( socket->request->netconn, bytes, len, &count, ovr ))) return err;
return (count == len) ? ERROR_SUCCESS : ERROR_INTERNAL_ERROR;
}
@ -3141,7 +3141,7 @@ static DWORD send_bytes( struct socket *socket, char *bytes, int len )
#define CONTROL_BIT (1 << 3)
static DWORD send_frame( struct socket *socket, enum socket_opcode opcode, USHORT status, const char *buf,
DWORD buflen, BOOL final )
DWORD buflen, BOOL final, WSAOVERLAPPED *ovr )
{
DWORD i = 0, j, offset = 2, len = buflen;
DWORD buffer_size, ret = 0, send_size;
@ -3177,6 +3177,7 @@ static DWORD send_frame( struct socket *socket, enum socket_opcode opcode, USHOR
buffer_size = len + offset;
if (len) buffer_size += 4;
assert( buffer_size - len < MAX_FRAME_BUFFER_SIZE );
if (ovr && buffer_size > MAX_FRAME_BUFFER_SIZE) return WSAEWOULDBLOCK;
if (buffer_size > socket->send_frame_buffer_size && socket->send_frame_buffer_size < MAX_FRAME_BUFFER_SIZE)
{
DWORD new_size;
@ -3217,7 +3218,7 @@ static DWORD send_frame( struct socket *socket, enum socket_opcode opcode, USHOR
while (j < buflen && offset < MAX_FRAME_BUFFER_SIZE)
socket->send_frame_buffer[offset++] = buf[j++] ^ mask[i++ % 4];
if ((ret = send_bytes( socket, socket->send_frame_buffer, offset ))) return ret;
if ((ret = send_bytes( socket, socket->send_frame_buffer, offset, ovr ))) return ret;
if (!(send_size -= offset)) break;
offset = 0;
@ -3227,6 +3228,16 @@ static DWORD send_frame( struct socket *socket, enum socket_opcode opcode, USHOR
return ERROR_SUCCESS;
}
static DWORD complete_send_frame( struct socket *socket, WSAOVERLAPPED *ovr )
{
DWORD retflags, len;
if (!WSAGetOverlappedResult( socket->request->netconn->socket, ovr, &len, TRUE, &retflags ))
return WSAGetLastError();
return ERROR_SUCCESS;
}
static void send_io_complete( struct object_header *hdr )
{
LONG count = InterlockedDecrement( &hdr->pending_sends );
@ -3265,11 +3276,12 @@ static void socket_send_complete( struct socket *socket, DWORD ret, WINHTTP_WEB_
}
}
static DWORD socket_send( struct socket *socket, WINHTTP_WEB_SOCKET_BUFFER_TYPE type, const void *buf, DWORD len )
static DWORD socket_send( struct socket *socket, WINHTTP_WEB_SOCKET_BUFFER_TYPE type, const void *buf, DWORD len,
WSAOVERLAPPED *ovr )
{
enum socket_opcode opcode = map_buffer_type( type );
return send_frame( socket, opcode, 0, buf, len, TRUE );
return send_frame( socket, opcode, 0, buf, len, TRUE, ovr );
}
static void CALLBACK task_socket_send( TP_CALLBACK_INSTANCE *instance, void *ctx, TP_WORK *work )
@ -3278,7 +3290,10 @@ static void CALLBACK task_socket_send( TP_CALLBACK_INSTANCE *instance, void *ctx
DWORD ret;
TRACE("running %p\n", work);
ret = socket_send( s->socket, s->type, s->buf, s->len );
if (s->complete_async) ret = complete_send_frame( s->socket, &s->ovr );
else ret = socket_send( s->socket, s->type, s->buf, s->len, NULL );
send_io_complete( &s->socket->hdr );
socket_send_complete( s->socket, ret, s->type, s->len );
@ -3289,7 +3304,7 @@ static void CALLBACK task_socket_send( TP_CALLBACK_INSTANCE *instance, void *ctx
DWORD WINAPI WinHttpWebSocketSend( HINTERNET hsocket, WINHTTP_WEB_SOCKET_BUFFER_TYPE type, void *buf, DWORD len )
{
struct socket *socket;
DWORD ret;
DWORD ret = 0;
TRACE("%p, %u, %p, %u\n", hsocket, type, buf, len);
@ -3314,24 +3329,53 @@ DWORD WINAPI WinHttpWebSocketSend( HINTERNET hsocket, WINHTTP_WEB_SOCKET_BUFFER_
if (socket->request->connect->hdr.flags & WINHTTP_FLAG_ASYNC)
{
BOOL async_send, complete_async = FALSE;
struct socket_send *s;
if (!(s = malloc( sizeof(*s) ))) return FALSE;
s->socket = socket;
s->type = type;
s->buf = buf;
s->len = len;
if (!(s = malloc( sizeof(*s) )))
{
release_object( &socket->hdr );
return ERROR_OUTOFMEMORY;
}
addref_object( &socket->hdr );
InterlockedIncrement( &socket->hdr.pending_sends );
if ((ret = queue_task( &socket->send_q, task_socket_send, s )))
async_send = InterlockedIncrement( &socket->hdr.pending_sends ) > 1 || socket->hdr.recursion_count >= 3;
if (!async_send)
{
memset( &s->ovr, 0, sizeof(s->ovr) );
if ((ret = socket_send( socket, type, buf, len, &s->ovr )) == WSA_IO_PENDING)
{
async_send = TRUE;
complete_async = TRUE;
}
else if (ret == WSAEWOULDBLOCK) async_send = TRUE;
}
if (async_send)
{
s->complete_async = complete_async;
s->socket = socket;
s->type = type;
s->buf = buf;
s->len = len;
addref_object( &socket->hdr );
if ((ret = queue_task( &socket->send_q, task_socket_send, s )))
{
InterlockedDecrement( &socket->hdr.pending_sends );
release_object( &socket->hdr );
free( s );
}
else ++socket->hdr.pending_sends;
}
else
{
InterlockedDecrement( &socket->hdr.pending_sends );
release_object( &socket->hdr );
free( s );
socket_send_complete( socket, ret, type, len );
ret = ERROR_SUCCESS;
}
}
else ret = socket_send( socket, type, buf, len );
else ret = socket_send( socket, type, buf, len, NULL );
release_object( &socket->hdr );
return ret;
@ -3418,7 +3462,7 @@ static void CALLBACK task_socket_send_pong( TP_CALLBACK_INSTANCE *instance, void
struct socket_send *s = ctx;
TRACE("running %p\n", work);
send_frame( s->socket, SOCKET_OPCODE_PONG, 0, NULL, 0, TRUE );
send_frame( s->socket, SOCKET_OPCODE_PONG, 0, NULL, 0, TRUE, NULL );
send_io_complete( &s->socket->hdr );
release_object( &s->socket->hdr );
@ -3445,7 +3489,7 @@ static DWORD socket_send_pong( struct socket *socket )
}
return ret;
}
return send_frame( socket, SOCKET_OPCODE_PONG, 0, NULL, 0, TRUE );
return send_frame( socket, SOCKET_OPCODE_PONG, 0, NULL, 0, TRUE, NULL );
}
static DWORD socket_drain( struct socket *socket )
@ -3611,7 +3655,7 @@ static DWORD socket_shutdown( struct socket *socket, USHORT status, const void *
DWORD ret;
stop_queue( &socket->send_q );
if (!(ret = send_frame( socket, SOCKET_OPCODE_CLOSE, status, reason, len, TRUE )))
if (!(ret = send_frame( socket, SOCKET_OPCODE_CLOSE, status, reason, len, TRUE, NULL )))
{
socket->state = SOCKET_STATE_SHUTDOWN;
}
@ -3697,7 +3741,7 @@ static DWORD socket_close( struct socket *socket, USHORT status, const void *rea
if (socket->state < SOCKET_STATE_SHUTDOWN)
{
stop_queue( &socket->send_q );
if ((ret = send_frame( socket, SOCKET_OPCODE_CLOSE, status, reason, len, TRUE ))) goto done;
if ((ret = send_frame( socket, SOCKET_OPCODE_CLOSE, status, reason, len, TRUE, NULL ))) goto done;
socket->state = SOCKET_STATE_SHUTDOWN;
}

View File

@ -62,6 +62,7 @@ struct notification
#define NF_ALLOW 0x0001 /* notification may or may not happen */
#define NF_WINE_ALLOW 0x0002 /* wine sends notification when it should not */
#define NF_SIGNAL 0x0004 /* signal wait handle when notified */
#define NF_MAIN_THREAD 0x0008 /* the operation completes synchronously and callback is called from the main thread */
struct info
{
@ -71,6 +72,7 @@ struct info
unsigned int index;
HANDLE wait;
unsigned int line;
DWORD main_thread_id;
DWORD last_thread_id;
DWORD last_status;
};
@ -111,6 +113,12 @@ static void CALLBACK check_notification( HINTERNET handle, DWORD_PTR context, DW
ok(status_ok, "%u: expected status 0x%08x got 0x%08x\n", info->line, info->test[info->index].status, status);
ok(function_ok, "%u: expected function %u got %u\n", info->line, info->test[info->index].function, info->function);
if (info->test[info->index].flags & NF_MAIN_THREAD)
{
ok(GetCurrentThreadId() == info->main_thread_id, "%u: expected callback to be called from the same thread\n",
info->line);
}
if (status_ok && function_ok && info->test[info->index++].flags & NF_SIGNAL)
{
SetEvent( info->wait );
@ -184,6 +192,7 @@ static void setup_test( struct info *info, enum api function, unsigned int line
info->test[info->index].function, function);
info->last_thread_id = 0xdeadbeef;
info->last_status = 0xdeadbeef;
info->main_thread_id = GetCurrentThreadId();
}
static void end_test( struct info *info, unsigned int line )
@ -658,8 +667,8 @@ static const struct notification websocket_test[] =
{ winhttp_receive_response, WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED },
{ winhttp_receive_response, WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE, NF_SIGNAL },
{ winhttp_websocket_complete_upgrade, WINHTTP_CALLBACK_STATUS_HANDLE_CREATED, NF_SIGNAL },
{ winhttp_websocket_send, WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE, NF_SIGNAL },
{ winhttp_websocket_send, WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE, NF_SIGNAL },
{ winhttp_websocket_send, WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE, NF_MAIN_THREAD | NF_SIGNAL },
{ winhttp_websocket_send, WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE, NF_MAIN_THREAD | NF_SIGNAL },
{ winhttp_websocket_shutdown, WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE, NF_SIGNAL },
{ winhttp_websocket_receive, WINHTTP_CALLBACK_STATUS_READ_COMPLETE, NF_SIGNAL },
{ winhttp_websocket_receive, WINHTTP_CALLBACK_STATUS_READ_COMPLETE, NF_SIGNAL },
@ -792,6 +801,9 @@ static void test_websocket(BOOL secure)
for (i = 0; i < 2; ++i)
{
/* The send is executed synchronously (even if sending a reasonably big buffer exceeding SSL buffer size).
* It is possible to trigger queueing the send into another thread but that involves sending a considerable
* amount of big enough buffers. */
setup_test( &info, winhttp_websocket_send, __LINE__ );
err = pWinHttpWebSocketSend( socket, WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE,
(void*)"hello", sizeof("hello") );

View File

@ -297,6 +297,8 @@ struct socket_send
WINHTTP_WEB_SOCKET_BUFFER_TYPE type;
const void *buf;
DWORD len;
WSAOVERLAPPED ovr;
BOOL complete_async;
};
struct socket_receive
@ -331,7 +333,7 @@ ULONG netconn_query_data_available( struct netconn * ) DECLSPEC_HIDDEN;
DWORD netconn_recv( struct netconn *, void *, size_t, int, int * ) DECLSPEC_HIDDEN;
DWORD netconn_resolve( WCHAR *, INTERNET_PORT, struct sockaddr_storage *, int ) DECLSPEC_HIDDEN;
DWORD netconn_secure_connect( struct netconn *, WCHAR *, DWORD, CredHandle *, BOOL ) DECLSPEC_HIDDEN;
DWORD netconn_send( struct netconn *, const void *, size_t, int * ) DECLSPEC_HIDDEN;
DWORD netconn_send( struct netconn *, const void *, size_t, int *, WSAOVERLAPPED * ) DECLSPEC_HIDDEN;
DWORD netconn_set_timeout( struct netconn *, BOOL, int ) DECLSPEC_HIDDEN;
BOOL netconn_is_alive( struct netconn * ) DECLSPEC_HIDDEN;
const void *netconn_get_certificate( struct netconn * ) DECLSPEC_HIDDEN;