This fixes the send part of the issue #52401 [1]: "Improper synchronization in sock_recv/sock_send leads to arbitrary reordering of completion of I/O requests", which was initially reported (outside Bugzilla) by Dongwan Kim [2].
[1] https://bugs.winehq.org/show_bug.cgi?id=52401 [2] https://www.winehq.org/pipermail/wine-devel/2021-May/186454.html
Jinoh Kang (9): server: Actually set initial status in set_async_direct_result handler. server: Add async_initial_status_callback. server: Defer postprocessing until after setting initial status in recv_socket handler. server: Defer postprocessing until after setting initial status in send_socket handler. server: Compact struct set_async_direct_result_reply. server: Add mark_pending field to set_async_direct_result request. server: Attempt to complete I/O request immediately in send_socket. ntdll: Don't call try_send before server call in sock_send. server: Replace redundant send_socket status fields with force_async boolean field.
dlls/ntdll/unix/socket.c | 84 +++++++++++++++++-------- dlls/ntdll/unix/sync.c | 9 +-- dlls/ntdll/unix/unix_private.h | 2 +- dlls/ws2_32/tests/sock.c | 14 ++--- server/async.c | 44 ++++++++++--- server/file.h | 2 + server/protocol.def | 7 ++- server/sock.c | 112 +++++++++++++++++++-------------- 8 files changed, 179 insertions(+), 95 deletions(-)
Commit 15483b1a126 (server: Allow calling async_handoff() with status code STATUS_ALERTED., 2022-02-10) introduced the set_async_direct_result handler which calls async_set_initial_status().
However, the async_set_initial_status() call does nothing since async->terminated is set, leaving the async in a confusing state (unknown_status = 1 but pending/completed).
So far, this issue is unlikely to have been a problem in practice for the following reasons:
1. async_set_initial_status() would have unset unknown_status, but it remains set instead. This is usually not a problem, since unknown_status is usually ever read by code paths effectively unreachable for non-device (e.g. socket) asyncs.
It would still potentially allow set_async_direct_result to be called multiple times, but it wouldn't actually happen in practice unless something goes wrong.
2. async_set_initial_status() would have set initial_status; however, it is left with the default value STATUS_PENDING. If the actual status is something other than that, the handler closes the wait handle and async_satisfied (the only realconsumer of initial_status) would never be called anyway.
For reasons above, this issue is not effectively observable or testable. Nonetheless, the current code does leave the async object in an inconsistent state.
Fix this by setting the initial_status directly (and unsetting unknown_status as well).
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- server/async.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/server/async.c b/server/async.c index 7d0ff10f7a4..f078f42f34c 100644 --- a/server/async.c +++ b/server/async.c @@ -769,7 +769,8 @@ DECL_HANDLER(set_async_direct_result) return; }
- async_set_initial_status( async, status ); + async->initial_status = status; + async->unknown_status = 0;
if (status == STATUS_PENDING) {
async->initial_status_callback is called whenever the initial_status is set. This can be used to do the post-processing after the initial I/O has been performed by the client, but before the async is actually completed or queued.
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- server/async.c | 32 ++++++++++++++++++++++++++++---- server/file.h | 2 ++ 2 files changed, 30 insertions(+), 4 deletions(-)
diff --git a/server/async.c b/server/async.c index f078f42f34c..a5afa4ebd39 100644 --- a/server/async.c +++ b/server/async.c @@ -62,6 +62,8 @@ struct async unsigned int comp_flags; /* completion flags */ async_completion_callback completion_callback; /* callback to be called on completion */ void *completion_callback_private; /* argument to completion_callback */ + async_initial_status_callback initial_status_callback; /* callback to be called on initial status set */ + void *initial_status_callback_private; /* argument to initial_status_callback */ };
static void async_dump( struct object *obj, int verbose ); @@ -139,11 +141,24 @@ static void async_satisfied( struct object *obj, struct wait_queue_entry *entry } }
+static void set_async_initial_status( struct async *async, unsigned int status ) +{ + async->initial_status = status; + if (async->initial_status_callback) + { + async->initial_status_callback(async->initial_status_callback_private, status); + async->initial_status_callback = NULL; + } +} + static void async_destroy( struct object *obj ) { struct async *async = (struct async *)obj; assert( obj->ops == &async_ops );
+ /* ensure initial_status_callback has been called */ + set_async_initial_status( async, async->initial_status ); + list_remove( &async->process_entry );
if (async->queue) @@ -281,6 +296,8 @@ struct async *create_async( struct fd *fd, struct thread *thread, const async_da async->comp_flags = 0; async->completion_callback = NULL; async->completion_callback_private = NULL; + async->initial_status_callback = NULL; + async->initial_status_callback_private = NULL;
if (iosb) async->iosb = (struct iosb *)grab_object( iosb ); else async->iosb = NULL; @@ -298,6 +315,13 @@ struct async *create_async( struct fd *fd, struct thread *thread, const async_da return async; }
+/* set a callback to be notified when the async initial status is set */ +void async_set_initial_status_callback( struct async *async, async_initial_status_callback func, void *private ) +{ + async->initial_status_callback = func; + async->initial_status_callback_private = private; +} + /* set the initial status of an async whose status was previously unknown * the initial status may be STATUS_PENDING */ void async_set_initial_status( struct async *async, unsigned int status ) @@ -305,7 +329,7 @@ void async_set_initial_status( struct async *async, unsigned int status ) assert( async->unknown_status ); if (!async->terminated) { - async->initial_status = status; + set_async_initial_status( async, status ); async->unknown_status = 0; } } @@ -363,7 +387,7 @@ obj_handle_t async_handoff( struct async *async, data_size_t *result, int force_ return async->wait_handle; }
- async->initial_status = get_error(); + set_async_initial_status( async, get_error() );
if (!async->pending && NT_ERROR( get_error() )) { @@ -402,7 +426,7 @@ obj_handle_t async_handoff( struct async *async, data_size_t *result, int force_ async->wait_handle = 0; } } - async->initial_status = async->iosb->status; + set_async_initial_status( async, async->iosb->status ); set_error( async->iosb->status ); return async->wait_handle; } @@ -769,7 +793,7 @@ DECL_HANDLER(set_async_direct_result) return; }
- async->initial_status = status; + set_async_initial_status( async, status ); async->unknown_status = 0;
if (status == STATUS_PENDING) diff --git a/server/file.h b/server/file.h index 9f9d4cd4e1a..1927374a164 100644 --- a/server/file.h +++ b/server/file.h @@ -219,6 +219,7 @@ extern struct object *create_serial( struct fd *fd ); /* async I/O functions */
typedef void (*async_completion_callback)( void *private ); +typedef void (*async_initial_status_callback)( void *private, unsigned int status );
extern void free_async_queue( struct async_queue *queue ); extern struct async *create_async( struct fd *fd, struct thread *thread, const async_data_t *data, struct iosb *iosb ); @@ -230,6 +231,7 @@ extern void async_set_result( struct object *obj, unsigned int status, apc_param extern void async_set_completion_callback( struct async *async, async_completion_callback func, void *private ); extern void async_set_unknown_status( struct async *async ); extern void set_async_pending( struct async *async ); +extern void async_set_initial_status_callback( struct async *async, async_initial_status_callback func, void *private ); extern void async_set_initial_status( struct async *async, unsigned int status ); extern void async_wake_obj( struct async *async ); extern int async_waiting( struct async_queue *queue );
This allows the client to postpone the initial I/O until after the server has actually queued the I/O request. The server should perform the postprocessing only after the initial I/O has been done.
In the case of recv_socket, the manipulation of event flags shall ideally be done *after* (not *before*) the client has attempted the initial I/O, since the readiness of the socket may change due to I/O.
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- server/sock.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-)
diff --git a/server/sock.c b/server/sock.c index 91f98556552..e2717b2c867 100644 --- a/server/sock.c +++ b/server/sock.c @@ -3393,6 +3393,24 @@ struct object *create_socket_device( struct object *root, const struct unicode_s return create_named_object( root, &socket_device_ops, name, attr, sd ); }
+static void recv_socket_initial_callback( void *private, unsigned int status ) +{ + struct sock *sock = (struct sock *)private; + sock->pending_events &= ~AFD_POLL_READ; + sock->reported_events &= ~AFD_POLL_READ; + sock_reselect( sock ); + release_object( sock ); +} + +static void recv_socket_initial_callback_oob( void *private, unsigned int status ) +{ + struct sock *sock = (struct sock *)private; + sock->pending_events &= ~AFD_POLL_OOB; + sock->reported_events &= ~AFD_POLL_OOB; + sock_reselect( sock ); + release_object( sock ); +} + DECL_HANDLER(recv_socket) { struct sock *sock = (struct sock *)get_handle_obj( current->process, req->async.handle, 0, &sock_ops ); @@ -3430,22 +3448,21 @@ DECL_HANDLER(recv_socket) if (status == STATUS_PENDING && !req->force_async && sock->nonblocking) status = STATUS_DEVICE_NOT_READY;
- sock->pending_events &= ~(req->oob ? AFD_POLL_OOB : AFD_POLL_READ); - sock->reported_events &= ~(req->oob ? AFD_POLL_OOB : AFD_POLL_READ); - if ((async = create_request_async( fd, get_fd_comp_flags( fd ), &req->async ))) { set_error( status );
+ async_set_initial_status_callback( async, + req->oob ? recv_socket_initial_callback_oob + : recv_socket_initial_callback, + grab_object( sock )); + if (timeout) async_set_timeout( async, timeout, STATUS_IO_TIMEOUT );
if (status == STATUS_PENDING || status == STATUS_ALERTED) queue_async( &sock->read_q, async );
- /* always reselect; we changed reported_events above */ - sock_reselect( sock ); - reply->wait = async_handoff( async, NULL, 0 ); reply->options = get_fd_options( fd ); reply->nonblocking = sock->nonblocking;
This allows the client to postpone the initial I/O until the server has queued the I/O request. The server should perform the postprocessing only after the initial I/O has been done.
In the case of send_socket, the manipulation of event flags shall ideally be done *after* (not *before*) the client has attempted the initial I/O, since the outbound queue status of the socket may change due to I/O. Also, the implicitly bound address is available only after the send* system call has been performed.
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- server/sock.c | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-)
diff --git a/server/sock.c b/server/sock.c index e2717b2c867..c769de05f8a 100644 --- a/server/sock.c +++ b/server/sock.c @@ -3471,16 +3471,9 @@ DECL_HANDLER(recv_socket) release_object( sock ); }
-DECL_HANDLER(send_socket) +static void send_socket_initial_callback( void *private, unsigned int status ) { - struct sock *sock = (struct sock *)get_handle_obj( current->process, req->async.handle, 0, &sock_ops ); - unsigned int status = req->status; - timeout_t timeout = 0; - struct async *async; - struct fd *fd; - - if (!sock) return; - fd = sock->fd; + struct sock *sock = (struct sock *)private;
if (sock->type == WS_SOCK_DGRAM) { @@ -3488,7 +3481,7 @@ DECL_HANDLER(send_socket) union unix_sockaddr unix_addr; socklen_t unix_len = sizeof(unix_addr);
- if (!sock->bound && !getsockname( get_unix_fd( fd ), &unix_addr.addr, &unix_len )) + if (!sock->bound && !getsockname( get_unix_fd( sock->fd ), &unix_addr.addr, &unix_len )) sock->addr_len = sockaddr_from_unix( &unix_addr, &sock->addr.addr, sizeof(sock->addr) ); sock->bound = 1; } @@ -3500,12 +3493,37 @@ DECL_HANDLER(send_socket) sock->reported_events &= ~AFD_POLL_WRITE; }
+ sock_reselect( sock ); + release_object( sock ); +} + +DECL_HANDLER(send_socket) +{ + struct sock *sock = (struct sock *)get_handle_obj( current->process, req->async.handle, 0, &sock_ops ); + unsigned int status = req->status; + timeout_t timeout = 0; + struct async *async; + struct fd *fd; + + if (!sock) return; + fd = sock->fd; + /* If we had a short write and the socket is nonblocking (and the client is * 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 && req->total && sock->nonblocking) + { + /* send() calls only clear and reselect events if unsuccessful. + * + * Since send_socket_initial_callback will observe success status (set + * by us) and skip clearing events, We shall clear them here. + */ + sock->pending_events &= ~AFD_POLL_WRITE; + sock->reported_events &= ~AFD_POLL_WRITE; + status = STATUS_SUCCESS; + }
/* send() returned EWOULDBLOCK or a short write, i.e. cannot send all data yet */ if (status == STATUS_DEVICE_NOT_READY && !sock->nonblocking) @@ -3535,15 +3553,14 @@ DECL_HANDLER(send_socket) } set_error( status );
+ async_set_initial_status_callback( async, send_socket_initial_callback, grab_object( sock )); + if (timeout) async_set_timeout( async, timeout, STATUS_IO_TIMEOUT );
if (status == STATUS_PENDING) queue_async( &sock->write_q, async );
- /* always reselect; we changed reported_events above */ - sock_reselect( sock ); - reply->wait = async_handoff( async, NULL, 0 ); reply->options = get_fd_options( fd ); release_object( async );
Get rid of the alignment padding introduced by apc_param_t information.
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- server/protocol.def | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/protocol.def b/server/protocol.def index 9d90544fa41..5b8a49f8aca 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -2166,8 +2166,8 @@ enum message_type /* Notify direct completion of async and close the wait handle if not blocking */ @REQ(set_async_direct_result) obj_handle_t handle; /* wait handle */ - apc_param_t information; /* IO_STATUS_BLOCK Information */ unsigned int status; /* completion status */ + apc_param_t information; /* IO_STATUS_BLOCK Information */ @REPLY obj_handle_t handle; /* wait handle, or NULL if closed */ @END
The client can set mark_pending to indicate that the full-blown I/O completion mechanism shall be triggered (asynchronous completion) even if the status indicates failure.
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- dlls/ntdll/unix/socket.c | 2 +- dlls/ntdll/unix/sync.c | 9 +++++---- dlls/ntdll/unix/unix_private.h | 2 +- server/async.c | 11 ++++++++--- server/protocol.def | 1 + 5 files changed, 16 insertions(+), 9 deletions(-)
diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index 23059e3cff8..2f8bd6e62bf 100644 --- a/dlls/ntdll/unix/socket.c +++ b/dlls/ntdll/unix/socket.c @@ -767,7 +767,7 @@ static NTSTATUS sock_recv( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, voi release_fileio( &async->io ); }
- if (alerted) set_async_direct_result( &wait_handle, status, information ); + 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; } diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index ee25dfd0099..0786454dad2 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -2514,7 +2514,7 @@ NTSTATUS WINAPI NtWaitForAlertByThreadId( const void *address, const LARGE_INTEG /* Notify direct completion of async and close the wait handle if it is no longer needed. * This function is a no-op (returns status as-is) if the supplied handle is NULL. */ -void set_async_direct_result( HANDLE *optional_handle, NTSTATUS status, ULONG_PTR information ) +void set_async_direct_result( HANDLE *optional_handle, NTSTATUS status, ULONG_PTR information, BOOL mark_pending ) { NTSTATUS ret;
@@ -2522,9 +2522,10 @@ void set_async_direct_result( HANDLE *optional_handle, NTSTATUS status, ULONG_PT
SERVER_START_REQ( set_async_direct_result ) { - req->handle = wine_server_obj_handle( *optional_handle ); - req->status = status; - req->information = information; + req->handle = wine_server_obj_handle( *optional_handle ); + req->status = status; + req->information = information; + req->mark_pending = mark_pending; ret = wine_server_call( req ); if (ret == STATUS_SUCCESS) *optional_handle = wine_server_ptr_handle( reply->handle ); diff --git a/dlls/ntdll/unix/unix_private.h b/dlls/ntdll/unix/unix_private.h index 75f03706401..b0458849adb 100644 --- a/dlls/ntdll/unix/unix_private.h +++ b/dlls/ntdll/unix/unix_private.h @@ -274,7 +274,7 @@ extern NTSTATUS get_device_info( int fd, struct _FILE_FS_DEVICE_INFORMATION *inf extern void init_files(void) DECLSPEC_HIDDEN; extern void init_cpu_info(void) DECLSPEC_HIDDEN; extern void add_completion( HANDLE handle, ULONG_PTR value, NTSTATUS status, ULONG info, BOOL async ) DECLSPEC_HIDDEN; -extern void set_async_direct_result( HANDLE *optional_handle, NTSTATUS status, ULONG_PTR information ); +extern void set_async_direct_result( HANDLE *optional_handle, NTSTATUS status, ULONG_PTR information, BOOL mark_pending );
extern void dbg_init(void) DECLSPEC_HIDDEN;
diff --git a/server/async.c b/server/async.c index a5afa4ebd39..39e65d65ad3 100644 --- a/server/async.c +++ b/server/async.c @@ -801,10 +801,15 @@ DECL_HANDLER(set_async_direct_result) async->direct_result = 0; async->pending = 1; } + else if (req->mark_pending) + { + async->pending = 1; + }
- /* if the I/O has completed successfully, the client would have already - * set the IOSB. therefore, we can skip waiting on wait_handle and do - * async_set_result() directly. + /* if the I/O has completed successfully (or unsuccessfully, and + * async->pending is set), the client would have already set the IOSB. + * therefore, we can do async_set_result() directly and let the client skip + * waiting on wait_handle. */ async_set_result( &async->obj, status, req->information );
diff --git a/server/protocol.def b/server/protocol.def index 5b8a49f8aca..cec2c644957 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -2168,6 +2168,7 @@ enum message_type obj_handle_t handle; /* wait handle */ unsigned int status; /* completion status */ apc_param_t information; /* IO_STATUS_BLOCK Information */ + int mark_pending; /* set async to pending before completion? */ @REPLY obj_handle_t handle; /* wait handle, or NULL if closed */ @END
Make send_socket alert the async immediately if poll() call detects that there are incoming data in the socket, bypassing the wineserver's main polling loop.
For sock_transmit, we always mark the async as pending and set the IOSB (unless async allocation has failed).
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- dlls/ntdll/unix/socket.c | 65 +++++++++++++++++++++++++++++++++------- server/protocol.def | 1 + server/sock.c | 22 +++++++++++++- 3 files changed, 76 insertions(+), 12 deletions(-)
diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index 2f8bd6e62bf..744458e19bd 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,31 @@ 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) + nonblocking = reply->nonblocking; + } + SERVER_END_REQ; + + alerted = status == STATUS_ALERTED; + if (alerted) + { + status = try_send( fd, async ); + if (status == STATUS_DEVICE_NOT_READY && (force_async || !nonblocking)) + status = STATUS_PENDING; + } + + if (status != STATUS_PENDING) + { + information = async->sent_len; + if (!NT_ERROR(status) || (wait_handle && !alerted)) { io->Status = status; - io->Information = async->sent_len; + io->Information = information; } + release_fileio( &async->io ); } - SERVER_END_REQ; - - if (status != STATUS_PENDING) 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 +1072,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 +1124,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/server/protocol.def b/server/protocol.def index cec2c644957..0111963193e 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/sock.c b/server/sock.c index c769de05f8a..a8892ef2071 100644 --- a/server/sock.c +++ b/server/sock.c @@ -3543,6 +3543,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 data; if there's no data + * 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 ))) { if (status == STATUS_SUCCESS) @@ -3558,11 +3577,12 @@ 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 );
reply->wait = async_handoff( async, NULL, 0 ); reply->options = get_fd_options( fd ); + reply->nonblocking = sock->nonblocking; release_object( async ); } release_object( sock );
Otherwise, try_send() call from sock_send() may race against try_send() call from async_send_proc(), shuffling the packet order.
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=52401 Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- dlls/ntdll/unix/socket.c | 13 ++----------- dlls/ws2_32/tests/sock.c | 14 +++++++------- 2 files changed, 9 insertions(+), 18 deletions(-)
diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index 744458e19bd..7499f0b37cd 100644 --- a/dlls/ntdll/unix/socket.c +++ b/dlls/ntdll/unix/socket.c @@ -908,21 +908,12 @@ static NTSTATUS sock_send( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, voi async->iov_cursor = 0; async->sent_len = 0;
- status = try_send( fd, async ); - - if (status != STATUS_SUCCESS && status != STATUS_DEVICE_NOT_READY) - { - release_fileio( &async->io ); - return status; - } - - if (status == STATUS_DEVICE_NOT_READY && force_async) - status = STATUS_PENDING; + status = force_async ? STATUS_PENDING : STATUS_DEVICE_NOT_READY;
SERVER_START_REQ( send_socket ) { req->status = status; - req->total = async->sent_len; + req->total = 0; req->async = server_async( handle, &async->io, event, apc, apc_user, iosb_client_ptr(io) ); status = wine_server_call( req ); wait_handle = wine_server_ptr_handle( reply->wait ); diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index 6b6267dc153..caf76868f89 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -7799,7 +7799,7 @@ static void test_shutdown(void) WSASetLastError(0xdeadbeef); ret = send(client, "test", 5, 0); ok(ret == -1, "got %d\n", ret); - todo_wine ok(WSAGetLastError() == WSAESHUTDOWN, "got error %u\n", WSAGetLastError()); + ok(WSAGetLastError() == WSAESHUTDOWN, "got error %u\n", WSAGetLastError());
ret = recv(server, buffer, sizeof(buffer), 0); ok(!ret, "got %d\n", ret); @@ -7846,7 +7846,7 @@ static void test_shutdown(void) WSASetLastError(0xdeadbeef); ret = send(server, "test", 5, 0); ok(ret == -1, "got %d\n", ret); - todo_wine ok(WSAGetLastError() == WSAESHUTDOWN, "got error %u\n", WSAGetLastError()); + ok(WSAGetLastError() == WSAESHUTDOWN, "got error %u\n", WSAGetLastError());
addrlen = sizeof(addr); ret = getpeername(client, (struct sockaddr *)&addr, &addrlen); @@ -7896,7 +7896,7 @@ static void test_shutdown(void) WSASetLastError(0xdeadbeef); ret = send(client, "test", 5, 0); ok(ret == -1, "got %d\n", ret); - todo_wine ok(WSAGetLastError() == WSAESHUTDOWN, "got error %u\n", WSAGetLastError()); + ok(WSAGetLastError() == WSAESHUTDOWN, "got error %u\n", WSAGetLastError());
ret = recv(server, buffer, sizeof(buffer), 0); ok(!ret, "got %d\n", ret); @@ -7914,7 +7914,7 @@ static void test_shutdown(void) WSASetLastError(0xdeadbeef); ret = send(server, "test", 5, 0); ok(ret == -1, "got %d\n", ret); - todo_wine ok(WSAGetLastError() == WSAESHUTDOWN, "got error %u\n", WSAGetLastError()); + ok(WSAGetLastError() == WSAESHUTDOWN, "got error %u\n", WSAGetLastError());
addrlen = sizeof(addr); ret = getpeername(client, (struct sockaddr *)&addr, &addrlen); @@ -8045,7 +8045,7 @@ static void test_shutdown(void) WSASetLastError(0xdeadbeef); ret = sendto(client, "test", 5, 0, (struct sockaddr *)&server_addr, sizeof(server_addr)); ok(ret == -1, "got %d\n", ret); - todo_wine ok(WSAGetLastError() == WSAESHUTDOWN, "got error %u\n", WSAGetLastError()); + ok(WSAGetLastError() == WSAESHUTDOWN, "got error %u\n", WSAGetLastError());
closesocket(client); closesocket(server); @@ -8126,7 +8126,7 @@ static void test_DisconnectEx(void) WSASetLastError(0xdeadbeef); ret = send(client, "test", 5, 0); ok(ret == -1, "expected failure\n"); - todo_wine ok(WSAGetLastError() == WSAESHUTDOWN, "got error %u\n", WSAGetLastError()); + ok(WSAGetLastError() == WSAESHUTDOWN, "got error %u\n", WSAGetLastError());
ret = recv(server, buffer, sizeof(buffer), 0); ok(!ret, "got %d\n", ret); @@ -8180,7 +8180,7 @@ static void test_DisconnectEx(void) WSASetLastError(0xdeadbeef); ret = send(client, "test", 5, 0); ok(ret == -1, "expected failure\n"); - todo_wine ok(WSAGetLastError() == WSAESHUTDOWN, "got error %u\n", WSAGetLastError()); + ok(WSAGetLastError() == WSAESHUTDOWN, "got error %u\n", WSAGetLastError());
ret = recv(server, buffer, sizeof(buffer), 0); ok(!ret, "got %d\n", ret);
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=108766
Your paranoid android.
=== debian11 (32 bit Chinese:China report) ===
ntdll: virtual: Timeout
The 'status' field of send_socket_request is always either STATUS_PENDING or STATUS_DEVICE_NOT_READY, and the 'total' field is always zero.
Replace the 'status' field with 'force_async' boolean field, and get rid of the 'total' field entirely.
Also, clean up the send_socket handler code a bit.
Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com --- dlls/ntdll/unix/socket.c | 8 ++----- server/protocol.def | 3 +-- server/sock.c | 50 +++++++--------------------------------- 3 files changed, 11 insertions(+), 50 deletions(-)
diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index 7499f0b37cd..ba6d65244ec 100644 --- a/dlls/ntdll/unix/socket.c +++ b/dlls/ntdll/unix/socket.c @@ -908,12 +908,9 @@ static NTSTATUS sock_send( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, voi async->iov_cursor = 0; async->sent_len = 0;
- status = force_async ? STATUS_PENDING : STATUS_DEVICE_NOT_READY; - SERVER_START_REQ( send_socket ) { - req->status = status; - req->total = 0; + req->force_async = force_async; req->async = server_async( handle, &async->io, event, apc, apc_user, iosb_client_ptr(io) ); status = wine_server_call( req ); wait_handle = wine_server_ptr_handle( reply->wait ); @@ -1109,8 +1106,7 @@ static NTSTATUS sock_transmit( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc,
SERVER_START_REQ( send_socket ) { - req->status = STATUS_PENDING; - req->total = 0; + req->force_async = 1; req->async = server_async( handle, &async->io, event, apc, apc_user, iosb_client_ptr(io) ); status = wine_server_call( req ); wait_handle = wine_server_ptr_handle( reply->wait ); diff --git a/server/protocol.def b/server/protocol.def index 0111963193e..c2b6735b659 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1462,8 +1462,7 @@ enum server_fd_type /* Perform a send on a socket */ @REQ(send_socket) async_data_t async; /* async I/O parameters */ - unsigned int status; /* status of initial call */ - unsigned int total; /* number of bytes already sent */ + int force_async; /* Force asynchronous mode? */ @REPLY obj_handle_t wait; /* handle to wait on for blocking send */ unsigned int options; /* device open options */ diff --git a/server/sock.c b/server/sock.c index a8892ef2071..5fa909f3fbc 100644 --- a/server/sock.c +++ b/server/sock.c @@ -3500,7 +3500,7 @@ static void send_socket_initial_callback( void *private, unsigned int status ) DECL_HANDLER(send_socket) { struct sock *sock = (struct sock *)get_handle_obj( current->process, req->async.handle, 0, &sock_ops ); - unsigned int status = req->status; + unsigned int status = STATUS_PENDING; timeout_t timeout = 0; struct async *async; struct fd *fd; @@ -3508,42 +3508,11 @@ DECL_HANDLER(send_socket) if (!sock) return; fd = sock->fd;
- /* If we had a short write and the socket is nonblocking (and the client is - * 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 && req->total && sock->nonblocking) - { - /* send() calls only clear and reselect events if unsuccessful. - * - * Since send_socket_initial_callback will observe success status (set - * by us) and skip clearing events, We shall clear them here. - */ - sock->pending_events &= ~AFD_POLL_WRITE; - sock->reported_events &= ~AFD_POLL_WRITE; + if (!req->force_async && !sock->nonblocking && is_fd_overlapped( fd )) + timeout = (timeout_t)sock->sndtimeo * -10000;
- status = STATUS_SUCCESS; - } - - /* send() returned EWOULDBLOCK or a short write, i.e. cannot send all data yet */ - if (status == STATUS_DEVICE_NOT_READY && !sock->nonblocking) - { - /* Set a timeout on the async if necessary. - * - * We want to do this *only* if the client gave us STATUS_DEVICE_NOT_READY. - * If the client gave us STATUS_PENDING, it expects the async to always - * block (it was triggered by WSASend*() with a valid OVERLAPPED - * structure) and for the timeout not to be respected. */ - if (is_fd_overlapped( fd )) - timeout = (timeout_t)sock->sndtimeo * -10000; - - status = STATUS_PENDING; - } - - 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 (sock->wr_shutdown) status = STATUS_PIPE_DISCONNECTED; + else if (!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 data; if there's no data @@ -3562,14 +3531,11 @@ DECL_HANDLER(send_socket) } }
+ if (status == STATUS_PENDING && !req->force_async && sock->nonblocking) + status = STATUS_DEVICE_NOT_READY; + if ((async = create_request_async( fd, get_fd_comp_flags( fd ), &req->async ))) { - if (status == STATUS_SUCCESS) - { - struct iosb *iosb = async_get_iosb( async ); - iosb->result = req->total; - release_object( iosb ); - } set_error( status );
async_set_initial_status_callback( async, send_socket_initial_callback, grab_object( sock ));