win: Implement non-overlapped pipe cancellation
This commit is contained in:
parent
a29797999b
commit
2d31a1f222
@ -196,7 +196,8 @@ if(WIN32)
|
|||||||
ws2_32
|
ws2_32
|
||||||
dbghelp
|
dbghelp
|
||||||
ole32
|
ole32
|
||||||
shell32)
|
shell32
|
||||||
|
synchronization)
|
||||||
list(APPEND uv_sources
|
list(APPEND uv_sources
|
||||||
src/win/async.c
|
src/win/async.c
|
||||||
src/win/core.c
|
src/win/core.c
|
||||||
|
|||||||
@ -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([SUNOS], [AS_CASE([$host_os],[solaris*], [true], [false])])
|
||||||
AM_CONDITIONAL([WINNT], [AS_CASE([$host_os],[mingw*], [true], [false])])
|
AM_CONDITIONAL([WINNT], [AS_CASE([$host_os],[mingw*], [true], [false])])
|
||||||
AS_CASE([$host_os],[mingw*], [
|
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], [
|
AS_CASE([$host_os], [solaris2.10], [
|
||||||
CFLAGS="$CFLAGS -DSUNOS_NO_IFADDRS"
|
CFLAGS="$CFLAGS -DSUNOS_NO_IFADDRS"
|
||||||
|
|||||||
@ -376,9 +376,11 @@ typedef struct {
|
|||||||
ULONG_PTR result; /* overlapped.Internal is reused to hold the result */\
|
ULONG_PTR result; /* overlapped.Internal is reused to hold the result */\
|
||||||
HANDLE pipeHandle; \
|
HANDLE pipeHandle; \
|
||||||
DWORD duplex_flags; \
|
DWORD duplex_flags; \
|
||||||
WCHAR* name; \
|
WCHAR* name; \
|
||||||
} connect; \
|
} connect; \
|
||||||
} u; \
|
} 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; \
|
struct uv_req_s* next_req; \
|
||||||
union { \
|
union { \
|
||||||
void* reserved2[1]; \
|
void* reserved2[1]; \
|
||||||
@ -478,7 +480,10 @@ typedef struct {
|
|||||||
|
|
||||||
#define uv_pipe_connection_fields \
|
#define uv_pipe_connection_fields \
|
||||||
uv_timer_t* eof_timer; \
|
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; \
|
DWORD ipc_remote_pid; \
|
||||||
union { \
|
union { \
|
||||||
uint32_t payload_remaining; \
|
uint32_t payload_remaining; \
|
||||||
@ -487,7 +492,7 @@ typedef struct {
|
|||||||
struct uv__queue ipc_xfer_queue; \
|
struct uv__queue ipc_xfer_queue; \
|
||||||
int ipc_xfer_queue_length; \
|
int ipc_xfer_queue_length; \
|
||||||
uv_write_t* non_overlapped_writes_tail; \
|
uv_write_t* non_overlapped_writes_tail; \
|
||||||
CRITICAL_SECTION readfile_thread_lock; \
|
CRITICAL_SECTION thread_lock; \
|
||||||
volatile HANDLE readfile_thread_handle;
|
volatile HANDLE readfile_thread_handle;
|
||||||
|
|
||||||
#define UV_PIPE_PRIVATE_FIELDS \
|
#define UV_PIPE_PRIVATE_FIELDS \
|
||||||
|
|||||||
@ -103,7 +103,7 @@ enum {
|
|||||||
UV_HANDLE_ZERO_READ = 0x00040000,
|
UV_HANDLE_ZERO_READ = 0x00040000,
|
||||||
UV_HANDLE_EMULATE_IOCP = 0x00080000,
|
UV_HANDLE_EMULATE_IOCP = 0x00080000,
|
||||||
UV_HANDLE_BLOCKING_WRITES = 0x00100000,
|
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 */
|
/* Used by uv_tcp_t and uv_udp_t handles */
|
||||||
UV_HANDLE_IPV6 = 0x00400000,
|
UV_HANDLE_IPV6 = 0x00400000,
|
||||||
|
|||||||
@ -134,6 +134,7 @@ int uv__pipe_write(uv_loop_t* loop,
|
|||||||
uv_stream_t* send_handle,
|
uv_stream_t* send_handle,
|
||||||
uv_write_cb cb);
|
uv_write_cb cb);
|
||||||
void uv__pipe_shutdown(uv_loop_t* loop, uv_pipe_t* handle, uv_shutdown_t* req);
|
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,
|
void uv__process_pipe_read_req(uv_loop_t* loop, uv_pipe_t* handle,
|
||||||
uv_req_t* req);
|
uv_req_t* req);
|
||||||
|
|||||||
388
src/win/pipe.c
388
src/win/pipe.c
@ -90,6 +90,13 @@ typedef struct {
|
|||||||
} uv__coalesced_write_t;
|
} 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_init(uv_pipe_t* pipe);
|
||||||
static void eof_timer_start(uv_pipe_t* pipe);
|
static void eof_timer_start(uv_pipe_t* pipe);
|
||||||
static void eof_timer_stop(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) {
|
mode_info.Mode & FILE_SYNCHRONOUS_IO_NONALERT) {
|
||||||
/* Non-overlapped pipe. */
|
/* Non-overlapped pipe. */
|
||||||
handle->flags |= UV_HANDLE_NON_OVERLAPPED_PIPE;
|
handle->flags |= UV_HANDLE_NON_OVERLAPPED_PIPE;
|
||||||
handle->pipe.conn.readfile_thread_handle = NULL;
|
handle->pipe.conn.non_overlapped_write_active = NULL;
|
||||||
InitializeCriticalSection(&handle->pipe.conn.readfile_thread_lock);
|
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 {
|
} else {
|
||||||
/* Overlapped pipe. Try to associate with IOCP. */
|
/* Overlapped pipe. Try to associate with IOCP. */
|
||||||
if (CreateIoCompletionPort(pipeHandle,
|
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)
|
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) {
|
if (handle->flags & UV_HANDLE_PIPESERVER) {
|
||||||
@ -996,51 +1005,69 @@ error:
|
|||||||
return 0;
|
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) {
|
void uv__pipe_interrupt_read(uv_pipe_t* handle) {
|
||||||
BOOL r;
|
|
||||||
|
|
||||||
if (!(handle->flags & UV_HANDLE_READ_PENDING))
|
if (!(handle->flags & UV_HANDLE_READ_PENDING))
|
||||||
return; /* No pending reads. */
|
return; /* No pending reads. */
|
||||||
if (handle->flags & UV_HANDLE_CANCELLATION_PENDING)
|
if (handle->flags & UV_HANDLE_READ_CANCELLATION_PENDING)
|
||||||
return; /* Already cancelled. */
|
return; /* Already cancelled. */
|
||||||
if (handle->handle == INVALID_HANDLE_VALUE)
|
if (handle->handle == INVALID_HANDLE_VALUE)
|
||||||
return; /* Pipe handle closed. */
|
return; /* Pipe handle closed. */
|
||||||
|
|
||||||
if (!(handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE)) {
|
if (!(handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE)) {
|
||||||
/* Cancel asynchronous read. */
|
/* 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);
|
assert(r || GetLastError() == ERROR_NOT_FOUND);
|
||||||
(void) r;
|
(void) r;
|
||||||
} else {
|
} else {
|
||||||
/* Cancel synchronous read (which is happening in the thread pool). */
|
uv__pipe_cancel_synchronous_io(
|
||||||
HANDLE thread;
|
&handle->pipe.conn.readfile_thread_handle,
|
||||||
volatile HANDLE* thread_ptr = &handle->pipe.conn.readfile_thread_handle;
|
&handle->pipe.conn.thread_lock);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set flag to indicate that read has been cancelled. */
|
/* 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
|
/* Cleans up uv_pipe_t (server or connection) and all resources associated with
|
||||||
* it. */
|
* it. */
|
||||||
void uv__pipe_close(uv_loop_t* loop, uv_pipe_t* handle) {
|
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)
|
if ((handle->flags & UV_HANDLE_CONNECTION)
|
||||||
&& handle->handle != INVALID_HANDLE_VALUE) {
|
&& 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. */
|
/* This will eventually destroy the write queue for us too. */
|
||||||
close_pipe(handle);
|
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) {
|
/* Register the current thread for cancellation via CancelSynchronousIo.
|
||||||
uv_read_t* req = (uv_read_t*) arg;
|
* Returns 0 on success, or a Windows error code on failure (including
|
||||||
uv_pipe_t* handle = (uv_pipe_t*) req->data;
|
* ERROR_OPERATION_ABORTED if pre-empted by uv__pipe_cancel_synchronous_io).
|
||||||
uv_loop_t* loop = handle->loop;
|
* On success, the caller must call uv__pipe_end_synchronous_io after the
|
||||||
volatile HANDLE* thread_ptr = &handle->pipe.conn.readfile_thread_handle;
|
* blocking operation. On failure, the caller must not. */
|
||||||
CRITICAL_SECTION* lock = &handle->pipe.conn.readfile_thread_lock;
|
static DWORD uv__pipe_begin_synchronous_io(volatile HANDLE* thread_ptr,
|
||||||
|
CRITICAL_SECTION* lock,
|
||||||
|
HANDLE* thread_out) {
|
||||||
HANDLE thread;
|
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(),
|
if (!DuplicateHandle(GetCurrentProcess(),
|
||||||
GetCurrentThread(),
|
GetCurrentThread(),
|
||||||
GetCurrentProcess(),
|
GetCurrentProcess(),
|
||||||
@ -1262,46 +1399,74 @@ static DWORD WINAPI uv_pipe_zero_readfile_thread_proc(void* arg) {
|
|||||||
0,
|
0,
|
||||||
FALSE,
|
FALSE,
|
||||||
DUPLICATE_SAME_ACCESS)) {
|
DUPLICATE_SAME_ACCESS)) {
|
||||||
err = GetLastError();
|
/* Even if we failed here, we still need to synchronize */
|
||||||
goto out1;
|
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);
|
EnterCriticalSection(lock);
|
||||||
if (*thread_ptr == INVALID_HANDLE_VALUE) {
|
if (*thread_ptr == INVALID_HANDLE_VALUE) {
|
||||||
/* uv__pipe_interrupt_read() cancelled reading before we got here. */
|
/* Cancelled before we got here. */
|
||||||
err = ERROR_OPERATION_ABORTED;
|
/* Acknowledge the cancellation and wake the waiting canceller. */
|
||||||
} else {
|
*thread_ptr = NULL;
|
||||||
/* Let main thread know which worker thread is doing the blocking read. */
|
LeaveCriticalSection(lock);
|
||||||
assert(*thread_ptr == NULL);
|
WakeByAddressSingle((HANDLE*) thread_ptr);
|
||||||
*thread_ptr = thread;
|
CloseHandle(thread);
|
||||||
|
return ERROR_OPERATION_ABORTED;
|
||||||
}
|
}
|
||||||
|
assert(*thread_ptr == NULL);
|
||||||
|
*thread_ptr = thread;
|
||||||
LeaveCriticalSection(lock);
|
LeaveCriticalSection(lock);
|
||||||
|
|
||||||
if (err)
|
*thread_out = thread;
|
||||||
goto out2;
|
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))
|
if (!ReadFile(handle->handle, &uv_zero_, 0, &bytes, NULL))
|
||||||
err = GetLastError();
|
err = GetLastError();
|
||||||
|
|
||||||
/* Let the main thread know the worker is past the point of blocking. */
|
uv__pipe_end_synchronous_io(thread_ptr, lock, thread);
|
||||||
assert(thread == *thread_ptr);
|
|
||||||
*thread_ptr = INVALID_HANDLE_VALUE;
|
|
||||||
|
|
||||||
/* Briefly acquire the mutex. Since the main thread holds the lock while it
|
done:
|
||||||
* 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. */
|
|
||||||
if (err)
|
if (err)
|
||||||
SET_REQ_ERROR(req, err);
|
SET_REQ_ERROR(req, err);
|
||||||
else
|
else
|
||||||
@ -1314,24 +1479,39 @@ out1:
|
|||||||
|
|
||||||
static DWORD WINAPI uv_pipe_writefile_thread_proc(void* parameter) {
|
static DWORD WINAPI uv_pipe_writefile_thread_proc(void* parameter) {
|
||||||
int result;
|
int result;
|
||||||
|
HANDLE thread;
|
||||||
DWORD bytes;
|
DWORD bytes;
|
||||||
|
DWORD err;
|
||||||
uv_write_t* req = (uv_write_t*) parameter;
|
uv_write_t* req = (uv_write_t*) parameter;
|
||||||
uv_pipe_t* handle = (uv_pipe_t*) req->handle;
|
uv_pipe_t* handle = (uv_pipe_t*) req->handle;
|
||||||
uv_loop_t* loop = handle->loop;
|
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 != NULL);
|
||||||
assert(req->type == UV_WRITE);
|
assert(req->type == UV_WRITE);
|
||||||
assert(handle->type == UV_NAMED_PIPE);
|
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,
|
result = WriteFile(handle->handle,
|
||||||
req->write_buffer.base,
|
req->write_buffer.base,
|
||||||
req->write_buffer.len,
|
req->write_buffer.len,
|
||||||
&bytes,
|
&bytes,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
if (!result) {
|
if (!result)
|
||||||
SET_REQ_ERROR(req, GetLastError());
|
err = GetLastError();
|
||||||
}
|
|
||||||
|
uv__pipe_end_synchronous_io(thread_ptr, lock, thread);
|
||||||
|
|
||||||
|
done:
|
||||||
|
if (err)
|
||||||
|
SET_REQ_ERROR(req, err);
|
||||||
SET_REQ_NWRITTEN(req, bytes);
|
SET_REQ_NWRITTEN(req, bytes);
|
||||||
|
|
||||||
POST_COMPLETION_FOR_REQ(loop, req);
|
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;
|
req = &handle->read_req;
|
||||||
|
|
||||||
if (handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE) {
|
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. */
|
handle->pipe.conn.readfile_thread_handle = NULL; /* Reset cancellation. */
|
||||||
if (!QueueUserWorkItem(&uv_pipe_zero_readfile_thread_proc,
|
if (!QueueUserWorkItem(&uv_pipe_zero_readfile_thread_proc,
|
||||||
req,
|
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) {
|
static void uv__queue_non_overlapped_write(uv_pipe_t* handle) {
|
||||||
uv_write_t* req = uv_remove_non_overlapped_write_req(handle);
|
uv_write_t* req = uv_remove_non_overlapped_write_req(handle);
|
||||||
if (req) {
|
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,
|
if (!QueueUserWorkItem(&uv_pipe_writefile_thread_proc,
|
||||||
req,
|
req,
|
||||||
WT_EXECUTELONGFUNCTION)) {
|
WT_EXECUTELONGFUNCTION)) {
|
||||||
@ -1643,13 +1807,17 @@ static int uv__pipe_write_data(uv_loop_t* loop,
|
|||||||
REGISTER_HANDLE_REQ(loop, handle);
|
REGISTER_HANDLE_REQ(loop, handle);
|
||||||
handle->reqs_pending++;
|
handle->reqs_pending++;
|
||||||
handle->stream.conn.write_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;
|
return 0;
|
||||||
} else if (handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE) {
|
} else if (handle->flags & UV_HANDLE_NON_OVERLAPPED_PIPE) {
|
||||||
req->write_buffer = write_buf;
|
req->write_buffer = write_buf;
|
||||||
uv__insert_non_overlapped_write_req(handle, req);
|
uv__insert_non_overlapped_write_req(handle, req);
|
||||||
if (handle->stream.conn.write_reqs_pending == 0) {
|
if (handle->stream.conn.write_reqs_pending == 0) {
|
||||||
uv__queue_non_overlapped_write(handle);
|
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. */
|
/* Request queued by the kernel. */
|
||||||
@ -2124,7 +2292,7 @@ void uv__process_pipe_read_req(uv_loop_t* loop,
|
|||||||
DWORD bytes_requested;
|
DWORD bytes_requested;
|
||||||
assert(handle->type == UV_NAMED_PIPE);
|
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);
|
DECREASE_PENDING_REQ_COUNT(handle);
|
||||||
eof_timer_stop(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);
|
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
|
/* 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,
|
* uv_write_t structure so we can pass the expected pointer to the callback,
|
||||||
* then free the heap-allocated write req. */
|
* 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--;
|
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 &&
|
if (handle->stream.conn.write_reqs_pending == 0 &&
|
||||||
uv__is_stream_shutting(handle))
|
uv__is_stream_shutting(handle))
|
||||||
uv__pipe_shutdown(loop, handle, handle->stream.conn.shutdown_req);
|
uv__pipe_shutdown(loop, handle, handle->stream.conn.shutdown_req);
|
||||||
|
|||||||
@ -266,16 +266,14 @@ int uv_write_cancel(uv_write_t* req) {
|
|||||||
|
|
||||||
switch (stream->type) {
|
switch (stream->type) {
|
||||||
case UV_TCP:
|
case UV_TCP:
|
||||||
handle = (HANDLE)((uv_tcp_t*)stream)->socket;
|
handle = (HANDLE) ((uv_tcp_t*) stream)->socket;
|
||||||
break;
|
break;
|
||||||
case UV_NAMED_PIPE:
|
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)) ==
|
if ((stream->flags & (UV_HANDLE_BLOCKING_WRITES | UV_HANDLE_NON_OVERLAPPED_PIPE)) ==
|
||||||
UV_HANDLE_NON_OVERLAPPED_PIPE) {
|
UV_HANDLE_NON_OVERLAPPED_PIPE) {
|
||||||
/* Non-overlapped, non-blocking writes run on the threadpool and we
|
return uv__pipe_write_cancel_non_overlapped((uv_pipe_t*) stream, req);
|
||||||
do not have a good way to cancel them. */
|
|
||||||
return UV_ENOTSUP;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -997,13 +997,13 @@ void uv_process_tty_read_line_req(uv_loop_t* loop, uv_tty_t* handle,
|
|||||||
&buf);
|
&buf);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!(handle->flags & UV_HANDLE_CANCELLATION_PENDING) &&
|
if (!(handle->flags & UV_HANDLE_READ_CANCELLATION_PENDING) &&
|
||||||
req->u.io.overlapped.InternalHigh != 0) {
|
req->u.io.overlapped.InternalHigh != 0) {
|
||||||
/* Read successful. TODO: read unicode, convert to utf-8 */
|
/* Read successful. TODO: read unicode, convert to utf-8 */
|
||||||
DWORD bytes = req->u.io.overlapped.InternalHigh;
|
DWORD bytes = req->u.io.overlapped.InternalHigh;
|
||||||
handle->read_cb((uv_stream_t*) handle, bytes, &buf);
|
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. */
|
/* 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)) {
|
if (!WriteConsoleInputW(handle->handle, &record, 1, &written)) {
|
||||||
return GetLastError();
|
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 */
|
/* Cancel line-buffered read if not already pending */
|
||||||
err = uv__cancel_read_console(handle);
|
err = uv__cancel_read_console(handle);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
handle->flags |= UV_HANDLE_CANCELLATION_PENDING;
|
handle->flags |= UV_HANDLE_READ_CANCELLATION_PENDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -1105,7 +1105,7 @@ static int uv__cancel_read_console(uv_tty_t* handle) {
|
|||||||
DWORD err = 0;
|
DWORD err = 0;
|
||||||
LONG status;
|
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
|
/* Hold the output lock during the cancellation, to ensure that further
|
||||||
writes don't interfere with the screen state. It will be the ReadConsole
|
writes don't interfere with the screen state. It will be the ReadConsole
|
||||||
|
|||||||
@ -112,6 +112,8 @@ TEST_DECLARE (tcp_write_in_a_row)
|
|||||||
TEST_DECLARE (tcp_try_write_error)
|
TEST_DECLARE (tcp_try_write_error)
|
||||||
TEST_DECLARE (tcp_write_queue_order)
|
TEST_DECLARE (tcp_write_queue_order)
|
||||||
TEST_DECLARE (tcp_write_cancel)
|
TEST_DECLARE (tcp_write_cancel)
|
||||||
|
TEST_DECLARE (pipe_write_cancel)
|
||||||
|
TEST_DECLARE (pipe_write_cancel_all)
|
||||||
TEST_DECLARE (tcp_write_nwritten)
|
TEST_DECLARE (tcp_write_nwritten)
|
||||||
TEST_DECLARE (pipe_write_nwritten)
|
TEST_DECLARE (pipe_write_nwritten)
|
||||||
TEST_DECLARE (tcp_open)
|
TEST_DECLARE (tcp_open)
|
||||||
@ -727,6 +729,8 @@ TASK_LIST_START
|
|||||||
|
|
||||||
TEST_ENTRY (tcp_write_queue_order)
|
TEST_ENTRY (tcp_write_queue_order)
|
||||||
TEST_ENTRY (tcp_write_cancel)
|
TEST_ENTRY (tcp_write_cancel)
|
||||||
|
TEST_ENTRY (pipe_write_cancel)
|
||||||
|
TEST_ENTRY (pipe_write_cancel_all)
|
||||||
TEST_ENTRY (tcp_write_nwritten)
|
TEST_ENTRY (tcp_write_nwritten)
|
||||||
TEST_ENTRY (pipe_write_nwritten)
|
TEST_ENTRY (pipe_write_nwritten)
|
||||||
|
|
||||||
|
|||||||
@ -28,22 +28,26 @@
|
|||||||
|
|
||||||
#define REQ_COUNT 100
|
#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 close_cb_called;
|
||||||
static int write_cb_called;
|
static int write_cb_called;
|
||||||
static int cancelled_count;
|
static int cancelled_count;
|
||||||
static int connected;
|
static int connected;
|
||||||
static int closing;
|
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) {
|
static void close_cb(uv_handle_t* handle) {
|
||||||
close_cb_called++;
|
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) {
|
static void connection_cb(uv_stream_t* tcp, int status) {
|
||||||
ASSERT_OK(status);
|
ASSERT_OK(status);
|
||||||
ASSERT_OK(uv_tcp_init(tcp->loop, &incoming));
|
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) {
|
static void write_cb(uv_write_t* req, int status) {
|
||||||
write_cb_called++;
|
write_cb_called++;
|
||||||
if (status == UV_ECANCELED && !closing)
|
if (status == UV_ECANCELED && closing == 0)
|
||||||
cancelled_count++;
|
cancelled_count++;
|
||||||
|
|
||||||
if (cancelled_count >= 5 && !closing) {
|
if (cancelled_count >= 5 && closing == 0) {
|
||||||
closing = 1;
|
closing++;
|
||||||
|
ASSERT_EQ(1, closing);
|
||||||
uv_close((uv_handle_t*) &client, close_cb);
|
uv_close((uv_handle_t*) &client, close_cb);
|
||||||
uv_close((uv_handle_t*) &server, close_cb);
|
uv_close((uv_handle_t*) &server, close_cb);
|
||||||
if (connected)
|
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_server;
|
||||||
static uv_tcp_t nwritten_client;
|
static uv_tcp_t nwritten_client;
|
||||||
@ -154,18 +304,14 @@ static int nwritten_cb_called;
|
|||||||
static size_t nwritten_value;
|
static size_t nwritten_value;
|
||||||
static char nwritten_buf_data[1024];
|
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) {
|
static void nwritten_write_cb(uv_write_t* req, int status) {
|
||||||
ASSERT_OK(status);
|
ASSERT_OK(status);
|
||||||
nwritten_cb_called++;
|
nwritten_cb_called++;
|
||||||
nwritten_value = uv_write_nwritten(req);
|
nwritten_value = uv_write_nwritten(req);
|
||||||
|
|
||||||
uv_close((uv_handle_t*) &nwritten_client, nwritten_close_cb);
|
uv_close((uv_handle_t*) &nwritten_client, close_cb);
|
||||||
uv_close((uv_handle_t*) &nwritten_server, nwritten_close_cb);
|
uv_close((uv_handle_t*) &nwritten_server, close_cb);
|
||||||
uv_close((uv_handle_t*) &nwritten_incoming, nwritten_close_cb);
|
uv_close((uv_handle_t*) &nwritten_incoming, close_cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nwritten_connection_cb(uv_stream_t* tcp, int status) {
|
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_client;
|
||||||
static uv_pipe_t pipe_server;
|
static uv_pipe_t pipe_server;
|
||||||
@ -228,17 +374,13 @@ static int pipe_cb_called;
|
|||||||
static size_t pipe_nwritten_value;
|
static size_t pipe_nwritten_value;
|
||||||
static char pipe_buf_data[1024];
|
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) {
|
static void pipe_write_cb(uv_write_t* req, int status) {
|
||||||
ASSERT_OK(status);
|
ASSERT_OK(status);
|
||||||
pipe_cb_called++;
|
pipe_cb_called++;
|
||||||
pipe_nwritten_value = uv_write_nwritten(req);
|
pipe_nwritten_value = uv_write_nwritten(req);
|
||||||
|
|
||||||
uv_close((uv_handle_t*) &pipe_client, pipe_close_cb);
|
uv_close((uv_handle_t*) &pipe_client, close_cb);
|
||||||
uv_close((uv_handle_t*) &pipe_server, pipe_close_cb);
|
uv_close((uv_handle_t*) &pipe_server, close_cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_IMPL(pipe_write_nwritten) {
|
TEST_IMPL(pipe_write_nwritten) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user