diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index 2f8bd6e62bf..62920a7e557 100644 --- a/dlls/ntdll/unix/socket.c +++ b/dlls/ntdll/unix/socket.c @@ -868,11 +868,13 @@ static NTSTATUS sock_send( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, voi const struct WS_sockaddr *addr, unsigned int addr_len, int unix_flags, int force_async ) { struct async_send_ioctl *async; + ULONG_PTR information; HANDLE wait_handle; DWORD async_size; NTSTATUS status; unsigned int i; ULONG options; + BOOL nonblocking, alerted; async_size = offsetof( struct async_send_ioctl, iov[count] ); @@ -925,16 +927,38 @@ static NTSTATUS sock_send( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, voi status = wine_server_call( req ); wait_handle = wine_server_ptr_handle( reply->wait ); options = reply->options; - if ((!NT_ERROR(status) || wait_handle) && status != STATUS_PENDING) - { - io->Status = status; - io->Information = async->sent_len; - } + nonblocking = reply->nonblocking; } SERVER_END_REQ; - if (status != STATUS_PENDING) release_fileio( &async->io ); + alerted = status == STATUS_ALERTED; + if (alerted) + { + status = try_send( fd, async ); + if (status == STATUS_DEVICE_NOT_READY && (force_async || !nonblocking)) + status = STATUS_PENDING; + /* If we had a short write and the socket is nonblocking (and we are + * not trying to force the operation to be asynchronous), return + * success. Windows actually refuses to send any data in this case, + * and returns EWOULDBLOCK, but we have no way of doing that. */ + if (status == STATUS_DEVICE_NOT_READY && async->sent_len) + status = STATUS_SUCCESS; + } + + if (status != STATUS_PENDING) + { + information = async->sent_len; + if (!NT_ERROR(status) || (wait_handle && !alerted)) + { + io->Status = status; + io->Information = information; + } + release_fileio( &async->io ); + } + else information = 0; + + if (alerted) set_async_direct_result( &wait_handle, status, information, FALSE ); if (wait_handle) status = wait_async( wait_handle, options & FILE_SYNCHRONOUS_IO_ALERT ); return status; } @@ -1055,7 +1079,9 @@ static NTSTATUS sock_transmit( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, socklen_t addr_len; HANDLE wait_handle; NTSTATUS status; + ULONG_PTR information; ULONG options; + BOOL alerted; addr_len = sizeof(addr); if (getpeername( fd, &addr.addr, &addr_len ) != 0) @@ -1105,16 +1131,40 @@ static NTSTATUS sock_transmit( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, status = wine_server_call( req ); wait_handle = wine_server_ptr_handle( reply->wait ); options = reply->options; - /* In theory we'd fill the iosb here, as above in sock_send(), but it's - * actually currently impossible to get STATUS_SUCCESS. The server will - * either return STATUS_PENDING or an error code, and in neither case - * should the iosb be filled. */ - if (!status) FIXME( "Unhandled success status." ); } SERVER_END_REQ; - if (status != STATUS_PENDING) release_fileio( &async->io ); + alerted = status == STATUS_ALERTED; + if (alerted) + { + status = try_transmit( fd, file_fd, async ); + if (status == STATUS_DEVICE_NOT_READY) + status = STATUS_PENDING; + } + if (status != STATUS_PENDING) + { + information = async->head_cursor + async->file_cursor + async->tail_cursor; + if (!NT_ERROR(status) || wait_handle) + { + io->Status = status; + io->Information = information; + } + release_fileio( &async->io ); + } + else information = 0; + + if (alerted) + { + set_async_direct_result( &wait_handle, status, information, TRUE ); + if (!(options & (FILE_SYNCHRONOUS_IO_ALERT | FILE_SYNCHRONOUS_IO_NONALERT))) + { + /* Pretend we always do async I/O. The client can always retrieve + * the actual I/O status via the IO_STATUS_BLOCK. + */ + status = STATUS_PENDING; + } + } if (wait_handle) status = wait_async( wait_handle, options & FILE_SYNCHRONOUS_IO_ALERT ); return status; } diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h index 75b48d7601c..b3a5d3ef3d5 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -1791,6 +1791,8 @@ struct send_socket_reply struct reply_header __header; obj_handle_t wait; unsigned int options; + int nonblocking; + char __pad_20[4]; }; @@ -6284,7 +6286,7 @@ union generic_reply /* ### protocol_version begin ### */ -#define SERVER_PROTOCOL_VERSION 747 +#define SERVER_PROTOCOL_VERSION 748 /* ### protocol_version end ### */ diff --git a/server/protocol.def b/server/protocol.def index 66c6c97b1e0..81b44aefd7c 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1467,6 +1467,7 @@ enum server_fd_type @REPLY obj_handle_t wait; /* handle to wait on for blocking send */ unsigned int options; /* device open options */ + int nonblocking; /* is socket non-blocking? */ @END diff --git a/server/request.h b/server/request.h index 434041490c0..32929c146a0 100644 --- a/server/request.h +++ b/server/request.h @@ -1056,7 +1056,8 @@ C_ASSERT( FIELD_OFFSET(struct send_socket_request, total) == 60 ); C_ASSERT( sizeof(struct send_socket_request) == 64 ); C_ASSERT( FIELD_OFFSET(struct send_socket_reply, wait) == 8 ); C_ASSERT( FIELD_OFFSET(struct send_socket_reply, options) == 12 ); -C_ASSERT( sizeof(struct send_socket_reply) == 16 ); +C_ASSERT( FIELD_OFFSET(struct send_socket_reply, nonblocking) == 16 ); +C_ASSERT( sizeof(struct send_socket_reply) == 24 ); C_ASSERT( FIELD_OFFSET(struct get_next_console_request_request, handle) == 12 ); C_ASSERT( FIELD_OFFSET(struct get_next_console_request_request, signal) == 16 ); C_ASSERT( FIELD_OFFSET(struct get_next_console_request_request, read) == 20 ); diff --git a/server/sock.c b/server/sock.c index 3e140319965..1beff85cbe0 100644 --- a/server/sock.c +++ b/server/sock.c @@ -3526,6 +3526,25 @@ DECL_HANDLER(send_socket) if ((status == STATUS_PENDING || status == STATUS_DEVICE_NOT_READY) && sock->wr_shutdown) status = STATUS_PIPE_DISCONNECTED; + if ((status == STATUS_PENDING || status == STATUS_DEVICE_NOT_READY) && !async_queued( &sock->write_q )) + { + /* If write_q is not empty, we cannot really tell if the already queued + * asyncs will not consume all available space; if there's no space + * available, the current request won't be immediately satiable. + */ + struct pollfd pollfd; + pollfd.fd = get_unix_fd( sock->fd ); + pollfd.events = POLLOUT; + pollfd.revents = 0; + if (poll(&pollfd, 1, 0) >= 0 && pollfd.revents) + { + /* Give the client opportunity to complete synchronously. + * If it turns out that the I/O request is not actually immediately satiable, + * the client may then choose to re-queue the async (with STATUS_PENDING). */ + status = STATUS_ALERTED; + } + } + if ((async = create_request_async( fd, get_fd_comp_flags( fd ), &req->async ))) { struct send_req *send_req; @@ -3548,7 +3567,7 @@ DECL_HANDLER(send_socket) if (timeout) async_set_timeout( async, timeout, STATUS_IO_TIMEOUT ); - if (status == STATUS_PENDING) + if (status == STATUS_PENDING || status == STATUS_ALERTED) { queue_async( &sock->write_q, async ); sock_reselect( sock ); @@ -3556,6 +3575,7 @@ DECL_HANDLER(send_socket) reply->wait = async_handoff( async, NULL, 0 ); reply->options = get_fd_options( fd ); + reply->nonblocking = sock->nonblocking; release_object( async ); } release_object( sock ); diff --git a/server/trace.c b/server/trace.c index 75bc35b6b56..8653755e8a6 100644 --- a/server/trace.c +++ b/server/trace.c @@ -2053,6 +2053,7 @@ static void dump_send_socket_reply( const struct send_socket_reply *req ) { fprintf( stderr, " wait=%04x", req->wait ); fprintf( stderr, ", options=%08x", req->options ); + fprintf( stderr, ", nonblocking=%d", req->nonblocking ); } static void dump_get_next_console_request_request( const struct get_next_console_request_request *req )