win: Implement non-overlapped pipe cancellation

This commit is contained in:
Keno Fischer 2026-02-21 03:34:05 +00:00
parent a29797999b
commit 2d31a1f222
10 changed files with 473 additions and 146 deletions

View File

@ -196,7 +196,8 @@ if(WIN32)
ws2_32
dbghelp
ole32
shell32)
shell32
synchronization)
list(APPEND uv_sources
src/win/async.c
src/win/core.c

View File

@ -74,7 +74,7 @@ AM_CONDITIONAL([OS400], [AS_CASE([$host_os],[os400], [true], [false])
AM_CONDITIONAL([SUNOS], [AS_CASE([$host_os],[solaris*], [true], [false])])
AM_CONDITIONAL([WINNT], [AS_CASE([$host_os],[mingw*], [true], [false])])
AS_CASE([$host_os],[mingw*], [
LIBS="$LIBS -lws2_32 -lpsapi -liphlpapi -luserenv -luser32 -ldbghelp -lole32 -lshell32"
LIBS="$LIBS -lws2_32 -lpsapi -liphlpapi -luserenv -luser32 -ldbghelp -lole32 -lshell32 -lsynchronization"
])
AS_CASE([$host_os], [solaris2.10], [
CFLAGS="$CFLAGS -DSUNOS_NO_IFADDRS"

View File

@ -376,9 +376,11 @@ typedef struct {
ULONG_PTR result; /* overlapped.Internal is reused to hold the result */\
HANDLE pipeHandle; \
DWORD duplex_flags; \
WCHAR* name; \
WCHAR* name; \
} connect; \
} u; \
/* Singly linked list of pending reqs. For non-overlapped pipes, also used \
* to keep track of reqs no yet submitted to the thread pool */ \
struct uv_req_s* next_req; \
union { \
void* reserved2[1]; \
@ -478,7 +480,10 @@ typedef struct {
#define uv_pipe_connection_fields \
uv_timer_t* eof_timer; \
uv_write_t dummy; /* TODO: retained for ABI compat; remove this in v2.x. */ \
/* TODO: This is here for ABI compat - remove in 2.x. */ \
uintptr_t dummy[sizeof(uv_write_t) / sizeof(uintptr_t) - 2]; \
uv_write_t* non_overlapped_write_active; \
volatile HANDLE writefile_thread_handle; \
DWORD ipc_remote_pid; \
union { \
uint32_t payload_remaining; \
@ -487,7 +492,7 @@ typedef struct {
struct uv__queue ipc_xfer_queue; \
int ipc_xfer_queue_length; \
uv_write_t* non_overlapped_writes_tail; \
CRITICAL_SECTION readfile_thread_lock; \
CRITICAL_SECTION thread_lock; \
volatile HANDLE readfile_thread_handle;
#define UV_PIPE_PRIVATE_FIELDS \

View File

@ -103,7 +103,7 @@ enum {
UV_HANDLE_ZERO_READ = 0x00040000,
UV_HANDLE_EMULATE_IOCP = 0x00080000,
UV_HANDLE_BLOCKING_WRITES = 0x00100000,
UV_HANDLE_CANCELLATION_PENDING = 0x00200000,
UV_HANDLE_READ_CANCELLATION_PENDING = 0x00200000,
/* Used by uv_tcp_t and uv_udp_t handles */
UV_HANDLE_IPV6 = 0x00400000,

View File

@ -134,6 +134,7 @@ int uv__pipe_write(uv_loop_t* loop,
uv_stream_t* send_handle,
uv_write_cb cb);
void uv__pipe_shutdown(uv_loop_t* loop, uv_pipe_t* handle, uv_shutdown_t* req);
int uv__pipe_write_cancel_non_overlapped(uv_pipe_t* handle, uv_write_t* req);
void uv__process_pipe_read_req(uv_loop_t* loop, uv_pipe_t* handle,
uv_req_t* req);

View File

@ -90,6 +90,13 @@ typedef struct {
} uv__coalesced_write_t;
static uv_write_t* uv__coalesced_write_user_req(uv_write_t* req) {
if (req->coalesced)
return container_of(req, uv__coalesced_write_t, req)->user_req;
return req;
}
static void eof_timer_init(uv_pipe_t* pipe);
static void eof_timer_start(uv_pipe_t* pipe);
static void eof_timer_stop(uv_pipe_t* pipe);
@ -518,8 +525,10 @@ static int uv__set_pipe_handle(uv_loop_t* loop,
mode_info.Mode & FILE_SYNCHRONOUS_IO_NONALERT) {
/* Non-overlapped pipe. */
handle->flags |= UV_HANDLE_NON_OVERLAPPED_PIPE;
handle->pipe.conn.readfile_thread_handle = NULL;
InitializeCriticalSection(&handle->pipe.conn.readfile_thread_lock);
handle->pipe.conn.non_overlapped_write_active = NULL;
handle->pipe.conn.readfile_thread_handle = INVALID_HANDLE_VALUE;
handle->pipe.conn.writefile_thread_handle = INVALID_HANDLE_VALUE;
InitializeCriticalSection(&handle->pipe.conn.thread_lock);
} else {
/* Overlapped pipe. Try to associate with IOCP. */
if (CreateIoCompletionPort(pipeHandle,
@ -679,7 +688,7 @@ void uv__pipe_endgame(uv_loop_t* loop, uv_pipe_t* handle) {
}
if (handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE)
DeleteCriticalSection(&handle->pipe.conn.readfile_thread_lock);
DeleteCriticalSection(&handle->pipe.conn.thread_lock);
}
if (handle->flags & UV_HANDLE_PIPESERVER) {
@ -996,51 +1005,69 @@ error:
return 0;
}
/* Cancel a synchronous I/O operation running in a thread pool thread.
* thread_ptr is the volatile handle set by the worker thread, and lock
* is held briefly to synchronize the handshake. */
static void uv__pipe_cancel_synchronous_io(volatile HANDLE* thread_ptr,
CRITICAL_SECTION* lock) {
HANDLE thread;
HANDLE expected_invalid = INVALID_HANDLE_VALUE;
EnterCriticalSection(lock);
thread = *thread_ptr;
if (thread == NULL) {
/* The thread pool thread has not yet reached the point of blocking, we
* can pre-empt it here. However, we still need to wait for the thread
* to acknowledge the interrupt. Otherwise it could race with the next
* request. It does this by setting *thread_ptr back to NULL. */
*thread_ptr = INVALID_HANDLE_VALUE;
do {
LeaveCriticalSection(lock);
WaitOnAddress(thread_ptr, &expected_invalid, sizeof(*thread_ptr), INFINITE);
EnterCriticalSection(lock);
} while (*thread_ptr != NULL);
/* Finally set this back to INVALID_HANDLE_VALUE to retain the
* invariant that it'll be INVALID_HANDLE_VALUE on exit from this function. */
*thread_ptr = INVALID_HANDLE_VALUE;
} else {
/* Spin until the thread has acknowledged (by changing *thread_ptr)
* that it is past the point of blocking. */
while (thread != INVALID_HANDLE_VALUE) {
BOOL r = CancelSynchronousIo(thread);
assert(r || GetLastError() == ERROR_NOT_FOUND);
LeaveCriticalSection(lock);
SwitchToThread();
EnterCriticalSection(lock);
thread = *thread_ptr;
}
}
LeaveCriticalSection(lock);
}
void uv__pipe_interrupt_read(uv_pipe_t* handle) {
BOOL r;
if (!(handle->flags & UV_HANDLE_READ_PENDING))
return; /* No pending reads. */
if (handle->flags & UV_HANDLE_CANCELLATION_PENDING)
if (handle->flags & UV_HANDLE_READ_CANCELLATION_PENDING)
return; /* Already cancelled. */
if (handle->handle == INVALID_HANDLE_VALUE)
return; /* Pipe handle closed. */
if (!(handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE)) {
/* Cancel asynchronous read. */
r = CancelIoEx(handle->handle, &handle->read_req.u.io.overlapped);
BOOL r = CancelIoEx(handle->handle, &handle->read_req.u.io.overlapped);
assert(r || GetLastError() == ERROR_NOT_FOUND);
(void) r;
} else {
/* Cancel synchronous read (which is happening in the thread pool). */
HANDLE thread;
volatile HANDLE* thread_ptr = &handle->pipe.conn.readfile_thread_handle;
EnterCriticalSection(&handle->pipe.conn.readfile_thread_lock);
thread = *thread_ptr;
if (thread == NULL) {
/* The thread pool thread has not yet reached the point of blocking, we
* can pre-empt it by setting thread_handle to INVALID_HANDLE_VALUE. */
*thread_ptr = INVALID_HANDLE_VALUE;
} else {
/* Spin until the thread has acknowledged (by setting the thread to
* INVALID_HANDLE_VALUE) that it is past the point of blocking. */
while (thread != INVALID_HANDLE_VALUE) {
r = CancelSynchronousIo(thread);
assert(r || GetLastError() == ERROR_NOT_FOUND);
SwitchToThread(); /* Yield thread. */
thread = *thread_ptr;
}
}
LeaveCriticalSection(&handle->pipe.conn.readfile_thread_lock);
uv__pipe_cancel_synchronous_io(
&handle->pipe.conn.readfile_thread_handle,
&handle->pipe.conn.thread_lock);
}
/* Set flag to indicate that read has been cancelled. */
handle->flags |= UV_HANDLE_CANCELLATION_PENDING;
handle->flags |= UV_HANDLE_READ_CANCELLATION_PENDING;
}
@ -1051,6 +1078,113 @@ void uv__pipe_read_stop(uv_pipe_t* handle) {
}
/* Remove the element after prev from the non-overlapped write queue.
* prev must be a node in the queue. Returns the removed element. */
static uv_write_t* uv__remove_non_overlapped_write_req_after(
uv_pipe_t* handle, uv_write_t* prev) {
uv_write_t* req;
req = (uv_write_t*) prev->next_req;
if (req == prev) {
/* Only element. */
handle->pipe.conn.non_overlapped_writes_tail = NULL;
} else {
prev->next_req = req->next_req;
if (req == handle->pipe.conn.non_overlapped_writes_tail)
handle->pipe.conn.non_overlapped_writes_tail = prev;
}
return req;
}
static uv_write_t* uv_remove_non_overlapped_write_req(uv_pipe_t* handle) {
if (handle->pipe.conn.non_overlapped_writes_tail == NULL)
return NULL;
return uv__remove_non_overlapped_write_req_after(
handle, handle->pipe.conn.non_overlapped_writes_tail);
}
/* Find and remove a specific request from the non-overlapped write queue.
* For coalesced writes, match against the user-facing req.
* Returns the actual queued req if found and removed, NULL otherwise. */
static uv_write_t* uv__remove_specific_non_overlapped_write_req(
uv_pipe_t* handle, uv_write_t* target) {
uv_write_t* tail;
uv_write_t* prev;
uv_write_t* curr;
tail = handle->pipe.conn.non_overlapped_writes_tail;
if (tail == NULL)
return NULL;
prev = tail;
curr = (uv_write_t*) tail->next_req;
do {
if (uv__coalesced_write_user_req(curr) == target)
return uv__remove_non_overlapped_write_req_after(handle, prev);
prev = curr;
curr = (uv_write_t*) curr->next_req;
} while (prev != tail);
return NULL;
}
int uv__pipe_write_cancel_non_overlapped(uv_pipe_t* handle, uv_write_t* req) {
uv_write_t* queued_req;
assert(handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE);
assert(!(handle->flags & UV_HANDLE_BLOCKING_WRITES));
/* On the thread pool - send the cancellation signal. */
if (handle->pipe.conn.non_overlapped_write_active != NULL &&
uv__coalesced_write_user_req(
handle->pipe.conn.non_overlapped_write_active) == req) {
/* N.B.: It's possible to end up here multiple times if `req` is cancelled
* again after an initial cancellation, but before the completion is processed.
* This is harmless - uv__pipe_cancel_synchronous_io will see that the thread
* has concluded and do nothing. */
uv__pipe_cancel_synchronous_io(
&handle->pipe.conn.writefile_thread_handle,
&handle->pipe.conn.thread_lock);
return 0;
}
/* If it's in the queue, remove and complete it as cancelled. */
queued_req = uv__remove_specific_non_overlapped_write_req(handle, req);
if (queued_req != NULL) {
SET_REQ_ERROR(queued_req, ERROR_OPERATION_ABORTED);
SET_REQ_NWRITTEN(queued_req, 0);
uv__insert_pending_req(handle->loop, (uv_req_t*) queued_req);
return 0;
}
/* Already completed (including previously cancelled). */
return 0;
}
/* Cancel all active and pending non-overlapped writes. */
static void uv__pipe_flush_non_overlapped_writes(uv_pipe_t* handle) {
uv_write_t* req;
/* Cancel the active write if there is one. */
if (handle->pipe.conn.non_overlapped_write_active != NULL)
uv__pipe_cancel_synchronous_io(
&handle->pipe.conn.writefile_thread_handle,
&handle->pipe.conn.thread_lock);
/* Drain the entire queue. */
while ((req = uv_remove_non_overlapped_write_req(handle)) != NULL) {
SET_REQ_ERROR(req, ERROR_OPERATION_ABORTED);
SET_REQ_NWRITTEN(req, 0);
uv__insert_pending_req(handle->loop, (uv_req_t*) req);
}
}
/* Cleans up uv_pipe_t (server or connection) and all resources associated with
* it. */
void uv__pipe_close(uv_loop_t* loop, uv_pipe_t* handle) {
@ -1095,6 +1229,15 @@ void uv__pipe_close(uv_loop_t* loop, uv_pipe_t* handle) {
if ((handle->flags & UV_HANDLE_CONNECTION)
&& handle->handle != INVALID_HANDLE_VALUE) {
if (handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE) {
/* The non-overlapped writer thread reads handle->handle. To avoid
* the possibility of a stale value, we must shoot it down now before we
* close the handle. While we're at it, cancel the whole queue, to
* avoid putting things on the thread pool only for them to come straight
* back with an invalid handle error. */
uv__pipe_flush_non_overlapped_writes(handle);
}
/* This will eventually destroy the write queue for us too. */
close_pipe(handle);
}
@ -1239,22 +1382,16 @@ int uv__pipe_listen(uv_pipe_t* handle, int backlog, uv_connection_cb cb) {
}
static DWORD WINAPI uv_pipe_zero_readfile_thread_proc(void* arg) {
uv_read_t* req = (uv_read_t*) arg;
uv_pipe_t* handle = (uv_pipe_t*) req->data;
uv_loop_t* loop = handle->loop;
volatile HANDLE* thread_ptr = &handle->pipe.conn.readfile_thread_handle;
CRITICAL_SECTION* lock = &handle->pipe.conn.readfile_thread_lock;
/* Register the current thread for cancellation via CancelSynchronousIo.
* Returns 0 on success, or a Windows error code on failure (including
* ERROR_OPERATION_ABORTED if pre-empted by uv__pipe_cancel_synchronous_io).
* On success, the caller must call uv__pipe_end_synchronous_io after the
* blocking operation. On failure, the caller must not. */
static DWORD uv__pipe_begin_synchronous_io(volatile HANDLE* thread_ptr,
CRITICAL_SECTION* lock,
HANDLE* thread_out) {
HANDLE thread;
DWORD bytes;
DWORD err;
assert(req->type == UV_READ);
assert(handle->type == UV_NAMED_PIPE);
err = 0;
/* Create a handle to the current thread. */
if (!DuplicateHandle(GetCurrentProcess(),
GetCurrentThread(),
GetCurrentProcess(),
@ -1262,46 +1399,74 @@ static DWORD WINAPI uv_pipe_zero_readfile_thread_proc(void* arg) {
0,
FALSE,
DUPLICATE_SAME_ACCESS)) {
err = GetLastError();
goto out1;
/* Even if we failed here, we still need to synchronize */
DWORD err = GetLastError();
EnterCriticalSection(lock);
*thread_ptr = *thread_ptr == INVALID_HANDLE_VALUE ? NULL : INVALID_HANDLE_VALUE;
LeaveCriticalSection(lock);
WakeByAddressSingle((HANDLE*) thread_ptr);
return err;
}
/* The lock needs to be held when thread handle is modified. */
EnterCriticalSection(lock);
if (*thread_ptr == INVALID_HANDLE_VALUE) {
/* uv__pipe_interrupt_read() cancelled reading before we got here. */
err = ERROR_OPERATION_ABORTED;
} else {
/* Let main thread know which worker thread is doing the blocking read. */
assert(*thread_ptr == NULL);
*thread_ptr = thread;
/* Cancelled before we got here. */
/* Acknowledge the cancellation and wake the waiting canceller. */
*thread_ptr = NULL;
LeaveCriticalSection(lock);
WakeByAddressSingle((HANDLE*) thread_ptr);
CloseHandle(thread);
return ERROR_OPERATION_ABORTED;
}
assert(*thread_ptr == NULL);
*thread_ptr = thread;
LeaveCriticalSection(lock);
if (err)
goto out2;
*thread_out = thread;
return 0;
}
/* Deregister the current thread after a blocking operation and synchronize
* with any in-progress cancellation. */
static void uv__pipe_end_synchronous_io(volatile HANDLE* thread_ptr,
CRITICAL_SECTION* lock,
HANDLE thread) {
/* Acquire the lock, set the thread handle to INVALID_HANDLE_VALUE to signal
* completion, then release. This synchronizes with the cancellation spin
* loop which releases the lock around SwitchToThread(). */
EnterCriticalSection(lock);
assert(thread == *thread_ptr);
*thread_ptr = INVALID_HANDLE_VALUE;
LeaveCriticalSection(lock);
CloseHandle(thread);
}
static DWORD WINAPI uv_pipe_zero_readfile_thread_proc(void* arg) {
uv_read_t* req = (uv_read_t*) arg;
uv_pipe_t* handle = (uv_pipe_t*) req->data;
uv_loop_t* loop = handle->loop;
volatile HANDLE* thread_ptr = &handle->pipe.conn.readfile_thread_handle;
CRITICAL_SECTION* lock = &handle->pipe.conn.thread_lock;
HANDLE thread;
DWORD bytes;
DWORD err;
assert(req->type == UV_READ);
assert(handle->type == UV_NAMED_PIPE);
err = uv__pipe_begin_synchronous_io(thread_ptr, lock, &thread);
if (err)
goto done;
/* Block the thread until data is available on the pipe, or the read is
* cancelled. */
if (!ReadFile(handle->handle, &uv_zero_, 0, &bytes, NULL))
err = GetLastError();
/* Let the main thread know the worker is past the point of blocking. */
assert(thread == *thread_ptr);
*thread_ptr = INVALID_HANDLE_VALUE;
uv__pipe_end_synchronous_io(thread_ptr, lock, thread);
/* Briefly acquire the mutex. Since the main thread holds the lock while it
* is spinning trying to cancel this thread's I/O, we will block here until
* it stops doing that. */
EnterCriticalSection(lock);
LeaveCriticalSection(lock);
out2:
/* Close the handle to the current thread. */
CloseHandle(thread);
out1:
/* Set request status and post a completion record to the IOCP. */
done:
if (err)
SET_REQ_ERROR(req, err);
else
@ -1314,24 +1479,39 @@ out1:
static DWORD WINAPI uv_pipe_writefile_thread_proc(void* parameter) {
int result;
HANDLE thread;
DWORD bytes;
DWORD err;
uv_write_t* req = (uv_write_t*) parameter;
uv_pipe_t* handle = (uv_pipe_t*) req->handle;
uv_loop_t* loop = handle->loop;
volatile HANDLE* thread_ptr = &handle->pipe.conn.writefile_thread_handle;
CRITICAL_SECTION* lock = &handle->pipe.conn.thread_lock;
assert(req != NULL);
assert(req->type == UV_WRITE);
assert(handle->type == UV_NAMED_PIPE);
bytes = 0;
err = uv__pipe_begin_synchronous_io(thread_ptr, lock, &thread);
if (err)
goto done;
result = WriteFile(handle->handle,
req->write_buffer.base,
req->write_buffer.len,
&bytes,
NULL);
if (!result) {
SET_REQ_ERROR(req, GetLastError());
}
if (!result)
err = GetLastError();
uv__pipe_end_synchronous_io(thread_ptr, lock, thread);
done:
if (err)
SET_REQ_ERROR(req, err);
SET_REQ_NWRITTEN(req, bytes);
POST_COMPLETION_FOR_REQ(loop, req);
@ -1389,6 +1569,7 @@ static void uv__pipe_queue_read(uv_loop_t* loop, uv_pipe_t* handle) {
req = &handle->read_req;
if (handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE) {
assert(handle->pipe.conn.readfile_thread_handle == INVALID_HANDLE_VALUE);
handle->pipe.conn.readfile_thread_handle = NULL; /* Reset cancellation. */
if (!QueueUserWorkItem(&uv_pipe_zero_readfile_thread_proc,
req,
@ -1483,30 +1664,13 @@ static void uv__insert_non_overlapped_write_req(uv_pipe_t* handle,
}
static uv_write_t* uv_remove_non_overlapped_write_req(uv_pipe_t* handle) {
uv_write_t* req;
if (handle->pipe.conn.non_overlapped_writes_tail) {
req = (uv_write_t*)handle->pipe.conn.non_overlapped_writes_tail->next_req;
if (req == handle->pipe.conn.non_overlapped_writes_tail) {
handle->pipe.conn.non_overlapped_writes_tail = NULL;
} else {
handle->pipe.conn.non_overlapped_writes_tail->next_req =
req->next_req;
}
return req;
} else {
/* queue empty */
return NULL;
}
}
static void uv__queue_non_overlapped_write(uv_pipe_t* handle) {
uv_write_t* req = uv_remove_non_overlapped_write_req(handle);
if (req) {
assert(handle->pipe.conn.non_overlapped_write_active == NULL);
assert(handle->pipe.conn.writefile_thread_handle == INVALID_HANDLE_VALUE);
handle->pipe.conn.non_overlapped_write_active = req;
handle->pipe.conn.writefile_thread_handle = NULL; /* Reset cancellation. */
if (!QueueUserWorkItem(&uv_pipe_writefile_thread_proc,
req,
WT_EXECUTELONGFUNCTION)) {
@ -1643,13 +1807,17 @@ static int uv__pipe_write_data(uv_loop_t* loop,
REGISTER_HANDLE_REQ(loop, handle);
handle->reqs_pending++;
handle->stream.conn.write_reqs_pending++;
POST_COMPLETION_FOR_REQ(loop, req);
uv__insert_pending_req(loop, (uv_req_t*)req);
return 0;
} else if (handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE) {
req->write_buffer = write_buf;
uv__insert_non_overlapped_write_req(handle, req);
if (handle->stream.conn.write_reqs_pending == 0) {
uv__queue_non_overlapped_write(handle);
/* There shouldn't have been any queued writes before, so we should have
* dispatched the request we just added. Sanity check the state
* synchronization here. */
assert(handle->pipe.conn.non_overlapped_writes_tail == NULL);
}
/* Request queued by the kernel. */
@ -2124,7 +2292,7 @@ void uv__process_pipe_read_req(uv_loop_t* loop,
DWORD bytes_requested;
assert(handle->type == UV_NAMED_PIPE);
handle->flags &= ~(UV_HANDLE_READ_PENDING | UV_HANDLE_CANCELLATION_PENDING);
handle->flags &= ~(UV_HANDLE_READ_PENDING | UV_HANDLE_READ_CANCELLATION_PENDING);
DECREASE_PENDING_REQ_COUNT(handle);
eof_timer_stop(handle);
@ -2202,6 +2370,20 @@ void uv__process_pipe_write_req(uv_loop_t* loop, uv_pipe_t* handle,
err = GET_REQ_ERROR(req);
/* For non-overlapped pipes, manage the write queue before unwrapping
* coalesced writes, since the coalesced wrapper is what's in the queue.
*
* If this request was the active write (dispatched to the thread pool),
* clear the active slot and dispatch the next queued write. Queue-cancelled
* writes (removed by uv__pipe_write_cancel or flush) were never dispatched,
* so they must not trigger another dispatch. */
if (handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE) {
if (req == handle->pipe.conn.non_overlapped_write_active) {
handle->pipe.conn.non_overlapped_write_active = NULL;
uv__queue_non_overlapped_write(handle);
}
}
/* If this was a coalesced write, extract pointer to the user_provided
* uv_write_t structure so we can pass the expected pointer to the callback,
* then free the heap-allocated write req. */
@ -2220,12 +2402,6 @@ void uv__process_pipe_write_req(uv_loop_t* loop, uv_pipe_t* handle,
handle->stream.conn.write_reqs_pending--;
if (handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE &&
handle->pipe.conn.non_overlapped_writes_tail) {
assert(handle->stream.conn.write_reqs_pending > 0);
uv__queue_non_overlapped_write(handle);
}
if (handle->stream.conn.write_reqs_pending == 0 &&
uv__is_stream_shutting(handle))
uv__pipe_shutdown(loop, handle, handle->stream.conn.shutdown_req);

View File

@ -266,16 +266,14 @@ int uv_write_cancel(uv_write_t* req) {
switch (stream->type) {
case UV_TCP:
handle = (HANDLE)((uv_tcp_t*)stream)->socket;
handle = (HANDLE) ((uv_tcp_t*) stream)->socket;
break;
case UV_NAMED_PIPE:
handle = ((uv_pipe_t*)stream)->handle;
handle = ((uv_pipe_t*) stream)->handle;
if ((stream->flags & (UV_HANDLE_BLOCKING_WRITES | UV_HANDLE_NON_OVERLAPPED_PIPE)) ==
UV_HANDLE_NON_OVERLAPPED_PIPE) {
/* Non-overlapped, non-blocking writes run on the threadpool and we
do not have a good way to cancel them. */
return UV_ENOTSUP;
return uv__pipe_write_cancel_non_overlapped((uv_pipe_t*) stream, req);
}
break;

View File

@ -997,13 +997,13 @@ void uv_process_tty_read_line_req(uv_loop_t* loop, uv_tty_t* handle,
&buf);
}
} else {
if (!(handle->flags & UV_HANDLE_CANCELLATION_PENDING) &&
if (!(handle->flags & UV_HANDLE_READ_CANCELLATION_PENDING) &&
req->u.io.overlapped.InternalHigh != 0) {
/* Read successful. TODO: read unicode, convert to utf-8 */
DWORD bytes = req->u.io.overlapped.InternalHigh;
handle->read_cb((uv_stream_t*) handle, bytes, &buf);
}
handle->flags &= ~UV_HANDLE_CANCELLATION_PENDING;
handle->flags &= ~UV_HANDLE_READ_CANCELLATION_PENDING;
}
/* Wait for more input events. */
@ -1086,13 +1086,13 @@ int uv__tty_read_stop(uv_tty_t* handle) {
if (!WriteConsoleInputW(handle->handle, &record, 1, &written)) {
return GetLastError();
}
} else if (!(handle->flags & UV_HANDLE_CANCELLATION_PENDING)) {
} else if (!(handle->flags & UV_HANDLE_READ_CANCELLATION_PENDING)) {
/* Cancel line-buffered read if not already pending */
err = uv__cancel_read_console(handle);
if (err)
return err;
handle->flags |= UV_HANDLE_CANCELLATION_PENDING;
handle->flags |= UV_HANDLE_READ_CANCELLATION_PENDING;
}
return 0;
@ -1105,7 +1105,7 @@ static int uv__cancel_read_console(uv_tty_t* handle) {
DWORD err = 0;
LONG status;
assert(!(handle->flags & UV_HANDLE_CANCELLATION_PENDING));
assert(!(handle->flags & UV_HANDLE_READ_CANCELLATION_PENDING));
/* Hold the output lock during the cancellation, to ensure that further
writes don't interfere with the screen state. It will be the ReadConsole

View File

@ -112,6 +112,8 @@ TEST_DECLARE (tcp_write_in_a_row)
TEST_DECLARE (tcp_try_write_error)
TEST_DECLARE (tcp_write_queue_order)
TEST_DECLARE (tcp_write_cancel)
TEST_DECLARE (pipe_write_cancel)
TEST_DECLARE (pipe_write_cancel_all)
TEST_DECLARE (tcp_write_nwritten)
TEST_DECLARE (pipe_write_nwritten)
TEST_DECLARE (tcp_open)
@ -727,6 +729,8 @@ TASK_LIST_START
TEST_ENTRY (tcp_write_queue_order)
TEST_ENTRY (tcp_write_cancel)
TEST_ENTRY (pipe_write_cancel)
TEST_ENTRY (pipe_write_cancel_all)
TEST_ENTRY (tcp_write_nwritten)
TEST_ENTRY (pipe_write_nwritten)

View File

@ -28,22 +28,26 @@
#define REQ_COUNT 100
static uv_tcp_t server;
static uv_tcp_t client;
static uv_tcp_t incoming;
static int close_cb_called;
static int write_cb_called;
static int cancelled_count;
static int connected;
static int closing;
static uv_write_t write_reqs[REQ_COUNT];
static char buf_data[16 * 1024];
static void close_cb(uv_handle_t* handle) {
close_cb_called++;
}
/*
* tcp_write_cancel
*/
static uv_tcp_t server;
static uv_tcp_t client;
static uv_tcp_t incoming;
static uv_write_t write_reqs[REQ_COUNT];
static char buf_data[16 * 1024];
static void connection_cb(uv_stream_t* tcp, int status) {
ASSERT_OK(status);
ASSERT_OK(uv_tcp_init(tcp->loop, &incoming));
@ -54,11 +58,12 @@ static void connection_cb(uv_stream_t* tcp, int status) {
static void write_cb(uv_write_t* req, int status) {
write_cb_called++;
if (status == UV_ECANCELED && !closing)
if (status == UV_ECANCELED && closing == 0)
cancelled_count++;
if (cancelled_count >= 5 && !closing) {
closing = 1;
if (cancelled_count >= 5 && closing == 0) {
closing++;
ASSERT_EQ(1, closing);
uv_close((uv_handle_t*) &client, close_cb);
uv_close((uv_handle_t*) &server, close_cb);
if (connected)
@ -144,7 +149,152 @@ TEST_IMPL(tcp_write_cancel) {
/*
* Test that uv_write_nwritten returns correct byte count on success.
* pipe_write_cancel / pipe_write_cancel_all
*
* uv_pipe() creates non-overlapped handles, so on Windows these exercise
* the synchronous (thread pool) write cancellation path. Half the writes
* use multiple buffers to exercise the coalesced write path.
*/
static uv_pipe_t pipe_cancel_writer;
static uv_pipe_t pipe_cancel_reader;
static uv_write_t pipe_cancel_write_reqs[REQ_COUNT];
static char pipe_cancel_buf_data[64 * 1024];
static int cancel_target;
static uv_write_cb pipe_cancel_cb;
static void pipe_cancel_setup(uv_loop_t* loop) {
uv_buf_t bufs[4];
int fds[2];
int r;
int i;
close_cb_called = 0;
write_cb_called = 0;
cancelled_count = 0;
closing = 0;
memset(pipe_cancel_buf_data, 'D', sizeof(pipe_cancel_buf_data));
ASSERT_OK(uv_pipe(fds, 0, 0));
ASSERT_OK(uv_pipe_init(loop, &pipe_cancel_writer, 0));
ASSERT_OK(uv_pipe_init(loop, &pipe_cancel_reader, 0));
ASSERT_OK(uv_pipe_open(&pipe_cancel_writer, fds[1]));
ASSERT_OK(uv_pipe_open(&pipe_cancel_reader, fds[0]));
bufs[0] = uv_buf_init(pipe_cancel_buf_data,
sizeof(pipe_cancel_buf_data) / 4);
bufs[1] = uv_buf_init(pipe_cancel_buf_data +
sizeof(pipe_cancel_buf_data) / 4,
sizeof(pipe_cancel_buf_data) / 4);
bufs[2] = uv_buf_init(pipe_cancel_buf_data +
sizeof(pipe_cancel_buf_data) / 2,
sizeof(pipe_cancel_buf_data) / 4);
bufs[3] = uv_buf_init(pipe_cancel_buf_data +
3 * sizeof(pipe_cancel_buf_data) / 4,
sizeof(pipe_cancel_buf_data) / 4);
/* Queue many writes to fill the pipe buffer. Alternate between single-buffer
* and multi-buffer writes so the latter exercise the coalesced write path
* on Windows. */
for (i = 0; i < REQ_COUNT; i++) {
if (i % 2 == 0) {
r = uv_write(&pipe_cancel_write_reqs[i],
(uv_stream_t*) &pipe_cancel_writer,
&bufs[0],
1,
pipe_cancel_cb);
} else {
r = uv_write(&pipe_cancel_write_reqs[i],
(uv_stream_t*) &pipe_cancel_writer,
bufs,
4,
pipe_cancel_cb);
}
ASSERT_OK(r);
}
}
static void pipe_cancel_close(void) {
if (closing == 0) {
closing++;
ASSERT_EQ(1, closing);
uv_close((uv_handle_t*) &pipe_cancel_writer, close_cb);
uv_close((uv_handle_t*) &pipe_cancel_reader, close_cb);
}
}
static void pipe_cancel_write_cb_tail(uv_write_t* req, int status) {
write_cb_called++;
if (status == UV_ECANCELED && closing == 0)
cancelled_count++;
if (cancelled_count >= cancel_target && closing == 0)
pipe_cancel_close();
}
TEST_IMPL(pipe_write_cancel) {
uv_loop_t* loop;
int i;
loop = uv_default_loop();
cancel_target = 5;
pipe_cancel_cb = pipe_cancel_write_cb_tail;
pipe_cancel_setup(loop);
/* Cancel the trailing writes which should be queued. */
for (i = REQ_COUNT - 5; i < REQ_COUNT; i++)
ASSERT_OK(uv_write_cancel(&pipe_cancel_write_reqs[i]));
ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT));
ASSERT_EQ(5, cancelled_count);
ASSERT_EQ(2, close_cb_called);
MAKE_VALGRIND_HAPPY(loop);
return 0;
}
static void pipe_cancel_write_cb_all(uv_write_t* req, int status) {
write_cb_called++;
if (status == UV_ECANCELED)
cancelled_count++;
}
TEST_IMPL(pipe_write_cancel_all) {
uv_loop_t* loop;
int i;
loop = uv_default_loop();
pipe_cancel_cb = pipe_cancel_write_cb_all;
pipe_cancel_setup(loop);
/* Cancel all writes. The first few may have already completed; one may be
* currently blocking in the thread pool (on Windows). Both cases are
* exercised by cancelling every request in order. */
for (i = 0; i < REQ_COUNT; i++)
uv_write_cancel(&pipe_cancel_write_reqs[i]);
/* Close the handles so the event loop can drain. */
uv_close((uv_handle_t*) &pipe_cancel_writer, close_cb);
uv_close((uv_handle_t*) &pipe_cancel_reader, close_cb);
ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT));
/* Every write must have gotten a callback. */
ASSERT_EQ(REQ_COUNT, write_cb_called);
/* We don't know exactly how many were cancelled vs already completed,
* but at least the tail end should have been cancelled. */
ASSERT_GT(cancelled_count, 0);
ASSERT_EQ(2, close_cb_called);
MAKE_VALGRIND_HAPPY(loop);
return 0;
}
/*
* tcp_write_nwritten
*/
static uv_tcp_t nwritten_server;
static uv_tcp_t nwritten_client;
@ -154,18 +304,14 @@ static int nwritten_cb_called;
static size_t nwritten_value;
static char nwritten_buf_data[1024];
static void nwritten_close_cb(uv_handle_t* handle) {
close_cb_called++;
}
static void nwritten_write_cb(uv_write_t* req, int status) {
ASSERT_OK(status);
nwritten_cb_called++;
nwritten_value = uv_write_nwritten(req);
uv_close((uv_handle_t*) &nwritten_client, nwritten_close_cb);
uv_close((uv_handle_t*) &nwritten_server, nwritten_close_cb);
uv_close((uv_handle_t*) &nwritten_incoming, nwritten_close_cb);
uv_close((uv_handle_t*) &nwritten_client, close_cb);
uv_close((uv_handle_t*) &nwritten_server, close_cb);
uv_close((uv_handle_t*) &nwritten_incoming, close_cb);
}
static void nwritten_connection_cb(uv_stream_t* tcp, int status) {
@ -219,7 +365,7 @@ TEST_IMPL(tcp_write_nwritten) {
/*
* Test that uv_write_nwritten returns correct byte count for pipes.
* pipe_write_nwritten
*/
static uv_pipe_t pipe_client;
static uv_pipe_t pipe_server;
@ -228,17 +374,13 @@ static int pipe_cb_called;
static size_t pipe_nwritten_value;
static char pipe_buf_data[1024];
static void pipe_close_cb(uv_handle_t* handle) {
close_cb_called++;
}
static void pipe_write_cb(uv_write_t* req, int status) {
ASSERT_OK(status);
pipe_cb_called++;
pipe_nwritten_value = uv_write_nwritten(req);
uv_close((uv_handle_t*) &pipe_client, pipe_close_cb);
uv_close((uv_handle_t*) &pipe_server, pipe_close_cb);
uv_close((uv_handle_t*) &pipe_client, close_cb);
uv_close((uv_handle_t*) &pipe_server, close_cb);
}
TEST_IMPL(pipe_write_nwritten) {