Cygwin: remove the fhandler_base_overlapped class

Also remove the 'was_nonblocking' flag, which was needed only for
fhandler_base_overlapped.
This commit is contained in:
Ken Brown 2021-08-26 18:00:27 -04:00 committed by Corinna Vinschen
parent f56206cd86
commit f002d02b17
3 changed files with 1 additions and 458 deletions

View File

@ -30,16 +30,10 @@ details. */
#include <asm/socket.h>
#include "cygwait.h"
#define MAX_OVERLAPPED_WRITE_LEN (64 * 1024 * 1024)
#define MIN_OVERLAPPED_WRITE_LEN (1 * 1024 * 1024)
static const int CHUNK_SIZE = 1024; /* Used for crlf conversions */
struct __cygwin_perfile *perfile_table;
HANDLE NO_COPY fhandler_base_overlapped::asio_done;
LONG NO_COPY fhandler_base_overlapped::asio_close_counter;
int
fhandler_base::puts_readahead (const char *s, size_t len)
{
@ -184,8 +178,6 @@ fhandler_base::set_flags (int flags, int supplied_bin)
bin = wbinary () || rbinary () ? O_BINARY : O_TEXT;
openflags = flags | bin;
if (openflags & O_NONBLOCK)
was_nonblocking (true);
bin &= O_BINARY;
rbinary (bin ? true : false);
@ -1222,88 +1214,6 @@ fhandler_base::close ()
return res;
}
DWORD WINAPI
flush_async_io (void *arg)
{
fhandler_base_overlapped *fh = (fhandler_base_overlapped *) arg;
debug_only_printf ("waiting for write I/O for %s", fh->get_name ());
DWORD nbytes;
bool res = GetOverlappedResult (fh->get_output_handle (),
fh->get_overlapped (), &nbytes, true);
debug_printf ("finished waiting for I/O from %s, res %d", fh->get_name (),
res);
fh->close ();
delete fh;
InterlockedDecrement (&fhandler_base_overlapped::asio_close_counter);
SetEvent (fhandler_base_overlapped::asio_done);
_my_tls._ctinfo->auto_release ();
return 0;
}
void
fhandler_base_overlapped::flush_all_async_io ()
{
while (asio_close_counter > 0)
if (WaitForSingleObject (asio_done, INFINITE) != WAIT_OBJECT_0)
{
system_printf ("WaitForSingleObject failed, possible data loss in pipe, %E");
break;
}
asio_close_counter = 0;
if (asio_done)
CloseHandle (asio_done);
}
/* Start a thread to handle closing of overlapped asynchronous I/O since
Windows amazingly does not seem to always flush I/O on close. */
void
fhandler_base_overlapped::check_later ()
{
set_close_on_exec (true);
char buf[MAX_PATH];
if (!asio_done
&& !(asio_done = CreateEvent (&sec_none_nih, false, false,
shared_name (buf, "asio",
GetCurrentProcessId ()))))
api_fatal ("CreateEvent failed, %E");
InterlockedIncrement (&asio_close_counter);
if (!new cygthread(flush_async_io, this, "flasio"))
api_fatal ("couldn't create a thread to track async I/O, %E");
debug_printf ("started thread to handle asynchronous closing for %s", get_name ());
}
int
fhandler_base_overlapped::close ()
{
int res;
int writer = (get_access () & GENERIC_WRITE);
/* Need to treat non-blocking I/O specially because Windows appears to
be brain-dead. We're checking here if the descriptor was ever set
to nonblocking, rather than checking if it's nonblocking at close time.
The reason is that applications may switch back to blocking (for the
sake of some other application accessing this descriptor) without
performaing any further I/O. These applications would suffer data
loss, which this workaround is trying to fix. */
if (writer && was_nonblocking () && has_ongoing_io ())
{
clone (HEAP_3_FHANDLER)->check_later ();
res = 0;
}
else
{
/* Cancelling seems to be necessary for cases where a reader is
still executing when a signal handler performs a close. */
if (!writer)
CancelIo (get_handle ());
destroy_overlapped ();
res = fhandler_base::close ();
}
return res;
}
int
fhandler_base::ioctl (unsigned int cmd, void *buf)
{
@ -1421,14 +1331,6 @@ fhandler_base::dup (fhandler_base *child, int)
return 0;
}
int
fhandler_base_overlapped::dup (fhandler_base *child, int flags)
{
int res = fhandler_base::dup (child, flags) ||
((fhandler_base_overlapped *) child)->setup_overlapped ();
return res;
}
int fhandler_base::fcntl (int cmd, intptr_t arg)
{
int res;
@ -1648,13 +1550,6 @@ fhandler_base::fixup_after_fork (HANDLE parent)
del_my_locks (after_fork);
}
void
fhandler_base_overlapped::fixup_after_fork (HANDLE parent)
{
setup_overlapped ();
fhandler_base::fixup_after_fork (parent);
}
void
fhandler_base::fixup_after_exec ()
{
@ -1663,12 +1558,6 @@ fhandler_base::fixup_after_exec ()
del_my_locks (after_exec);
mandatory_locking (false);
}
void
fhandler_base_overlapped::fixup_after_exec ()
{
setup_overlapped ();
fhandler_base::fixup_after_exec ();
}
bool
fhandler_base::is_nonblocking ()
@ -1682,8 +1571,6 @@ fhandler_base::set_nonblocking (int yes)
int current = openflags & O_NONBLOCK_MASK;
int new_flags = yes ? (!current ? O_NONBLOCK : current) : 0;
openflags = (openflags & ~O_NONBLOCK_MASK) | new_flags;
if (new_flags)
was_nonblocking (true);
}
int
@ -1946,273 +1833,3 @@ fhandler_base::fpathconf (int v)
}
return -1;
}
/* Overlapped I/O */
int __reg1
fhandler_base_overlapped::setup_overlapped ()
{
OVERLAPPED *ov = get_overlapped_buffer ();
memset (ov, 0, sizeof (*ov));
set_overlapped (ov);
ov->hEvent = CreateEvent (&sec_none_nih, true, true, NULL);
io_pending = false;
return ov->hEvent ? 0 : -1;
}
void __reg1
fhandler_base_overlapped::destroy_overlapped ()
{
OVERLAPPED *ov = get_overlapped ();
if (ov && ov->hEvent)
{
SetEvent (ov->hEvent);
CloseHandle (ov->hEvent);
ov->hEvent = NULL;
}
io_pending = false;
get_overlapped () = NULL;
}
bool __reg1
fhandler_base_overlapped::has_ongoing_io ()
{
if (!io_pending)
return false;
if (!IsEventSignalled (get_overlapped ()->hEvent))
return true;
io_pending = false;
DWORD nbytes;
GetOverlappedResult (get_output_handle (), get_overlapped (), &nbytes, false);
return false;
}
fhandler_base_overlapped::wait_return __reg3
fhandler_base_overlapped::wait_overlapped (bool inres, bool writing, DWORD *bytes, bool nonblocking, DWORD len)
{
if (!get_overlapped ())
return inres ? overlapped_success : overlapped_error;
wait_return res = overlapped_unknown;
DWORD err;
if (inres)
/* handle below */;
else if ((err = GetLastError ()) != ERROR_IO_PENDING)
res = overlapped_error;
else if (!nonblocking)
/* handle below */;
else if (!writing)
SetEvent (get_overlapped ()->hEvent); /* Force immediate WFMO return */
else
{
*bytes = len; /* Assume that this worked */
io_pending = true; /* but don't allow subsequent */
res = overlapped_success; /* writes until completed */
}
if (res == overlapped_unknown)
{
DWORD wfres = cygwait (get_overlapped ()->hEvent);
HANDLE h = writing ? get_output_handle () : get_handle ();
BOOL wores;
if (isclosed ())
{
switch (wfres)
{
case WAIT_OBJECT_0:
err = ERROR_INVALID_HANDLE;
break;
case WAIT_SIGNALED:
err = ERROR_INVALID_AT_INTERRUPT_TIME;
break;
default:
err = GetLastError ();
break;
}
res = overlapped_error;
}
else
{
/* Cancelling here to prevent races. It's possible that the I/O has
completed already, in which case this is a no-op. Otherwise,
WFMO returned because 1) This is a non-blocking call, 2) a signal
arrived, or 3) the operation was cancelled. These cases may be
overridden by the return of GetOverlappedResult which could detect
that I/O completion occurred. */
CancelIo (h);
wores = GetOverlappedResult (h, get_overlapped (), bytes, false);
err = GetLastError ();
ResetEvent (get_overlapped ()->hEvent); /* Probably not needed but CYA */
debug_printf ("wfres %u, wores %d, bytes %u", wfres, wores, *bytes);
if (wores)
res = overlapped_success; /* operation succeeded */
else if (wfres == WAIT_OBJECT_0 + 1)
{
err = ERROR_INVALID_AT_INTERRUPT_TIME; /* forces an EINTR below */
debug_printf ("signal");
res = overlapped_error;
}
else if (wfres == WAIT_OBJECT_0 + 2)
pthread::static_cancel_self (); /* never returns */
else if (nonblocking)
res = overlapped_nonblocking_no_data; /* more handling below */
else
{
debug_printf ("GetOverlappedResult failed, h %p, bytes %u, %E", h, *bytes);
res = overlapped_error;
}
}
}
if (res == overlapped_success)
{
debug_printf ("normal %s, %u bytes ispipe() %d", writing ? "write" : "read", *bytes, ispipe ());
if (*bytes == 0 && !writing && ispipe ())
res = overlapped_nullread;
}
else if (res == overlapped_nonblocking_no_data)
{
*bytes = (DWORD) -1;
set_errno (EAGAIN);
debug_printf ("no data to read for nonblocking I/O");
}
else if (err == ERROR_HANDLE_EOF || err == ERROR_BROKEN_PIPE)
{
debug_printf ("EOF, %E");
*bytes = 0;
res = overlapped_success;
if (writing && err == ERROR_BROKEN_PIPE)
raise (SIGPIPE);
}
else
{
debug_printf ("res %u, Win32 Error %u", (unsigned) res, err);
*bytes = (DWORD) -1;
__seterrno_from_win_error (err);
if (writing && err == ERROR_NO_DATA)
raise (SIGPIPE);
}
return res;
}
void __reg3
fhandler_base_overlapped::raw_read (void *ptr, size_t& len)
{
DWORD nbytes;
bool keep_looping;
do
{
bool res = ReadFile (get_handle (), ptr, len, &nbytes,
get_overlapped ());
switch (wait_overlapped (res, false, &nbytes, is_nonblocking ()))
{
case overlapped_nullread:
keep_looping = true;
break;
default: /* Added to quiet gcc */
case overlapped_success:
case overlapped_error:
keep_looping = false;
break;
}
}
while (keep_looping);
len = (nbytes == (DWORD) -1) ? (size_t) -1 : (size_t) nbytes;
}
ssize_t __reg3
fhandler_base_overlapped::raw_write (const void *ptr, size_t len)
{
size_t nbytes;
if (has_ongoing_io ())
{
set_errno (EAGAIN);
nbytes = (size_t) -1;
}
else
{
size_t chunk;
if (!max_atomic_write || len < max_atomic_write)
chunk = MIN (len, INT_MAX);
else if (is_nonblocking ())
chunk = len = max_atomic_write;
else
chunk = max_atomic_write;
/* MSDN "WriteFile" contains the following note: "Accessing the output
buffer while a write operation is using the buffer may lead to
corruption of the data written from that buffer. [...] This can
be particularly problematic when using an asynchronous file handle.
(https://msdn.microsoft.com/en-us/library/windows/desktop/aa365747)
MSDN "Synchronous and Asynchronous I/O" contains the following note:
"Do not deallocate or modify [...] the data buffer until all
asynchronous I/O operations to the file object have been completed."
(https://msdn.microsoft.com/en-us/library/windows/desktop/aa365683)
This problem is a non-issue for blocking I/O, but it can lead to
problems when using nonblocking I/O. Consider:
- The application uses a static buffer in repeated calls to
non-blocking write.
- The previous write returned with success, but the overlapped I/O
operation is ongoing.
- The application copies the next set of data to the static buffer,
thus overwriting data still accessed by the previous write call.
--> potential data corruption.
What we do here is to allocate a per-fhandler buffer big enough
to perform the maximum atomic operation from, copy the user space
data over to this buffer and then call NtWriteFile on this buffer.
This decouples the write operation from the user buffer and the
user buffer can be reused without data corruption issues.
Since no further write can occur while we're still having ongoing
I/O, this should be reasanably safe.
Note: We only have proof that this problem actually occurs on Wine
yet. However, the MSDN language indicates that this may be a real
problem on real Windows as well. */
if (is_nonblocking ())
{
if (!atomic_write_buf)
atomic_write_buf = cmalloc_abort (HEAP_BUF, max_atomic_write);
ptr = memcpy (atomic_write_buf, ptr, chunk);
}
nbytes = 0;
DWORD nbytes_now = 0;
/* Write to fd in smaller chunks, accumulating a total.
If there's an error, just return the accumulated total
unless the first write fails, in which case return value
from wait_overlapped(). */
while (nbytes < len)
{
size_t left = len - nbytes;
size_t len1;
if (left > chunk)
len1 = chunk;
else
len1 = left;
bool res = WriteFile (get_output_handle (), ptr, len1, &nbytes_now,
get_overlapped ());
switch (wait_overlapped (res, true, &nbytes_now,
is_nonblocking (), len1))
{
case overlapped_success:
ptr = ((char *) ptr) + chunk;
nbytes += nbytes_now;
break;
case overlapped_error:
len = 0; /* terminate loop */
case overlapped_unknown:
case overlapped_nullread:
case overlapped_nonblocking_no_data:
break;
}
}
if (!nbytes)
nbytes = (nbytes_now == (DWORD) -1) ? (size_t) -1 : nbytes_now;
}
return nbytes;
}

View File

@ -178,16 +178,12 @@ class fhandler_base
unsigned need_fork_fixup : 1; /* Set if need to fixup after fork. */
unsigned isclosed : 1; /* Set when fhandler is closed. */
unsigned mandatory_locking : 1; /* Windows mandatory locking */
unsigned was_nonblocking : 1; /* Set when setting O_NONBLOCK. Never
reset. This is for the sake of
fhandler_base_overlapped::close. */
public:
status_flags () :
rbinary (0), rbinset (0), wbinary (0), wbinset (0), nohandle (0),
did_lseek (0), query_open (no_query), close_on_exec (0),
need_fork_fixup (0), isclosed (0), mandatory_locking (0),
was_nonblocking (0)
need_fork_fixup (0), isclosed (0), mandatory_locking (0)
{}
} status, open_status;
@ -289,7 +285,6 @@ class fhandler_base
IMPLEMENT_STATUS_FLAG (bool, need_fork_fixup)
IMPLEMENT_STATUS_FLAG (bool, isclosed)
IMPLEMENT_STATUS_FLAG (bool, mandatory_locking)
IMPLEMENT_STATUS_FLAG (bool, was_nonblocking)
int get_default_fmode (int flags);
@ -1170,74 +1165,6 @@ class fhandler_socket_unix : public fhandler_socket
#endif /* __WITH_AF_UNIX */
class fhandler_base_overlapped: public fhandler_base
{
static HANDLE asio_done;
static LONG asio_close_counter;
protected:
enum wait_return
{
overlapped_unknown = 0,
overlapped_success,
overlapped_nonblocking_no_data,
overlapped_nullread,
overlapped_error
};
bool io_pending;
OVERLAPPED io_status;
OVERLAPPED *overlapped;
size_t max_atomic_write;
void *atomic_write_buf;
public:
wait_return __reg3 wait_overlapped (bool, bool, DWORD *, bool, DWORD = 0);
int __reg1 setup_overlapped ();
void __reg1 destroy_overlapped ();
virtual void __reg3 raw_read (void *ptr, size_t& len);
virtual ssize_t __reg3 raw_write (const void *ptr, size_t len);
OVERLAPPED *&get_overlapped () {return overlapped;}
OVERLAPPED *get_overlapped_buffer () {return &io_status;}
void set_overlapped (OVERLAPPED *ov) {overlapped = ov;}
fhandler_base_overlapped (): io_pending (false), overlapped (NULL), max_atomic_write (0), atomic_write_buf (NULL)
{
memset (&io_status, 0, sizeof io_status);
}
bool __reg1 has_ongoing_io ();
void fixup_after_fork (HANDLE);
void fixup_after_exec ();
int close ();
int dup (fhandler_base *child, int);
void check_later ();
static void __reg1 flush_all_async_io ();;
fhandler_base_overlapped (void *) {}
~fhandler_base_overlapped ()
{
if (atomic_write_buf)
cfree (atomic_write_buf);
}
virtual void copy_from (fhandler_base *x)
{
pc.free_strings ();
*this = *reinterpret_cast<fhandler_base_overlapped *> (x);
atomic_write_buf = NULL;
_copy_from_reset_helper ();
}
virtual fhandler_base_overlapped *clone (cygheap_types malloc_type = HEAP_FHANDLER)
{
void *ptr = (void *) ccalloc (malloc_type, 1, sizeof (fhandler_base_overlapped));
fhandler_base_overlapped *fh = new (ptr) fhandler_base_overlapped (ptr);
fh->copy_from (this);
return fh;
}
friend DWORD WINAPI flush_async_io (void *);
};
class fhandler_pipe: public fhandler_base
{
private:

View File

@ -108,7 +108,6 @@ close_all_files (bool norelease)
if (!have_execed && cygheap->ctty)
cygheap->close_ctty ();
fhandler_base_overlapped::flush_all_async_io ();
if (h)
SetStdHandle (STD_ERROR_HANDLE, h);
cygheap->fdtab.unlock ();