From bb466278713a68d68fd507cb8f2ace6142f0a58c Mon Sep 17 00:00:00 2001 From: Ken Brown Date: Mon, 15 Apr 2019 15:43:57 +0000 Subject: [PATCH] Cygwin: FIFO: improve raw_write Don't set the write end of the pipe to non-blocking mode if the FIFO is opened in blocking mode. In fhandler_fifo::raw_write in blocking mode, wait for the write to complete rather than returning -1 with EAGAIN. If the amount to write is large, write in smaller chunks (of size determined by a new data member max_atomic_write), as in fhandler_base_overlapped. For convenience, add two new NTSTATUS codes, STATUS_THREAD_SIGNALED and STATUS_THREAD_CANCELED, to ntdll.h. --- winsup/cygwin/fhandler.h | 1 + winsup/cygwin/fhandler_fifo.cc | 99 ++++++++++++++++++++++++++-------- winsup/cygwin/ntdll.h | 4 +- 3 files changed, 82 insertions(+), 22 deletions(-) diff --git a/winsup/cygwin/fhandler.h b/winsup/cygwin/fhandler.h index aea02c2b3..fd205a6db 100644 --- a/winsup/cygwin/fhandler.h +++ b/winsup/cygwin/fhandler.h @@ -1273,6 +1273,7 @@ class fhandler_fifo: public fhandler_base int nhandlers, nconnected; af_unix_spinlock_t _fifo_client_lock; bool reader, writer, duplexer; + size_t max_atomic_write; bool __reg2 wait (HANDLE); NTSTATUS npfs_handle (HANDLE &); HANDLE create_pipe_instance (bool); diff --git a/winsup/cygwin/fhandler_fifo.cc b/winsup/cygwin/fhandler_fifo.cc index fe4c67bdf..d2f8b99bc 100644 --- a/winsup/cygwin/fhandler_fifo.cc +++ b/winsup/cygwin/fhandler_fifo.cc @@ -39,7 +39,8 @@ STATUS_PIPE_EMPTY simply means there's no data to be read. */ fhandler_fifo::fhandler_fifo (): fhandler_base (), read_ready (NULL), write_ready (NULL), listen_client_thr (NULL), lct_termination_evt (NULL), nhandlers (0), - nconnected (0), reader (false), writer (false), duplexer (false) + nconnected (0), reader (false), writer (false), duplexer (false), + max_atomic_write (DEFAULT_PIPEBUFSIZE) { pipe_name_buf[0] = L'\0'; need_fork_fixup (true); @@ -559,7 +560,7 @@ fhandler_fifo::open (int flags, mode_t) NTSTATUS status = open_pipe (); if (NT_SUCCESS (status)) { - set_pipe_non_blocking (get_handle (), true); + set_pipe_non_blocking (get_handle (), flags & O_NONBLOCK); if (!arm (write_ready)) res = error_set_errno; else @@ -661,28 +662,84 @@ ssize_t __reg3 fhandler_fifo::raw_write (const void *ptr, size_t len) { ssize_t ret = -1; - NTSTATUS status; + size_t nbytes = 0, chunk; + NTSTATUS status = STATUS_SUCCESS; IO_STATUS_BLOCK io; + HANDLE evt = NULL; - status = NtWriteFile (get_handle (), NULL, NULL, NULL, &io, - (PVOID) ptr, len, NULL, NULL); - if (NT_SUCCESS (status)) - { - /* NtWriteFile returns success with # of bytes written == 0 in - case writing on a non-blocking pipe fails if the pipe buffer - is full. */ - if (io.Information == 0) - set_errno (EAGAIN); - else - ret = io.Information; - } - else if (STATUS_PIPE_IS_CLOSED (status)) - { - set_errno (EPIPE); - raise (SIGPIPE); - } + if (len <= max_atomic_write) + chunk = len; + else if (is_nonblocking ()) + chunk = len = max_atomic_write; else - __seterrno_from_nt_status (status); + chunk = max_atomic_write; + + /* Create a wait event if the FIFO is in blocking mode. */ + if (!is_nonblocking () && !(evt = CreateEvent (NULL, false, false, NULL))) + return -1; + + /* Write in chunks, accumulating a total. If there's an error, just + return the accumulated total unless the first write fails, in + which case return -1. */ + while (nbytes < len) + { + ULONG_PTR nbytes_now = 0; + size_t left = len - nbytes; + size_t len1; + if (left > chunk) + len1 = chunk; + else + len1 = left; + nbytes_now = 0; + status = NtWriteFile (get_handle (), evt, NULL, NULL, &io, + (PVOID) ptr, len1, NULL, NULL); + if (evt && status == STATUS_PENDING) + { + DWORD waitret = cygwait (evt, cw_infinite, cw_cancel | cw_sig_eintr); + switch (waitret) + { + case WAIT_OBJECT_0: + status = io.Status; + break; + case WAIT_SIGNALED: + status = STATUS_THREAD_SIGNALED; + break; + case WAIT_CANCELED: + status = STATUS_THREAD_CANCELED; + break; + default: + break; + } + } + if (NT_SUCCESS (status)) + { + nbytes_now = io.Information; + /* NtWriteFile returns success with # of bytes written == 0 + if writing on a non-blocking pipe fails because the pipe + buffer doesn't have sufficient space. */ + if (nbytes_now == 0) + set_errno (EAGAIN); + ptr = ((char *) ptr) + chunk; + nbytes += nbytes_now; + } + else if (STATUS_PIPE_IS_CLOSED (status)) + { + set_errno (EPIPE); + raise (SIGPIPE); + } + else + __seterrno_from_nt_status (status); + if (nbytes_now == 0) + len = 0; /* Terminate loop. */ + if (nbytes > 0) + ret = nbytes; + } + if (evt) + CloseHandle (evt); + if (status == STATUS_THREAD_SIGNALED && !_my_tls.call_signal_handler ()) + set_errno (EINTR); + else if (status == STATUS_THREAD_CANCELED) + pthread::static_cancel_self (); return ret; } diff --git a/winsup/cygwin/ntdll.h b/winsup/cygwin/ntdll.h index b41b99b4b..e19cc8ab5 100644 --- a/winsup/cygwin/ntdll.h +++ b/winsup/cygwin/ntdll.h @@ -15,7 +15,9 @@ extern GUID __cygwin_socket_guid; #define CYGWIN_SOCKET_GUID (&__cygwin_socket_guid) -/* custom status code: */ +/* Custom Cygwin-only status codes. */ +#define STATUS_THREAD_SIGNALED ((NTSTATUS)0xe0000001) +#define STATUS_THREAD_CANCELED ((NTSTATUS)0xe0000002) #define STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION ((NTSTATUS) 0xe0000269) /* Simplify checking for a transactional error code. */