Commit b88ccd9e authored by Adam Simpkins's avatar Adam Simpkins Committed by Facebook GitHub Bot

implement readvFull() and writevFull() using read()/write() on Windows

Summary:
On Windows, implement `readvFull()` and `writevFull()` by calling `read()` and
`write()` repeatedly, rather than `readv()` and `writev()`.

Windows does not provide native implementations of `readv()` and `writev()`,
so the code was previously using implementations provided by folly in
`folly/portability/SysUio.cpp`.  The folly implementations attempted to lock
the file to provide similar atomicity guarantees that `readv()` and `writev()`
provide on POSIX.  However, callers of `readvFull()` and `writevFull()` don't
care about this atomicity (since they are willing to do multiple `readv()` and
`writev()` calls if necessary).  Therefore this locking behavior is not
needed.  Locking in this case is undesirable because it adds extra overhead,
and fails in some cases.  For instance, attempting to lock stderr blocks for 9
seconds before eventually failing.

Differential Revision: D21367446

fbshipit-source-id: b2ae3c9c5da977402336c750d3d21ba9825527d9
parent e5c56348
...@@ -147,6 +147,7 @@ ssize_t pwriteFull(int fd, const void* buf, size_t count, off_t offset) { ...@@ -147,6 +147,7 @@ ssize_t pwriteFull(int fd, const void* buf, size_t count, off_t offset) {
return wrapFull(pwrite, fd, const_cast<void*>(buf), count, offset); return wrapFull(pwrite, fd, const_cast<void*>(buf), count, offset);
} }
#ifndef _WIN32
ssize_t readvFull(int fd, iovec* iov, int count) { ssize_t readvFull(int fd, iovec* iov, int count) {
return wrapvFull(readv, fd, iov, count); return wrapvFull(readv, fd, iov, count);
} }
...@@ -162,6 +163,30 @@ ssize_t writevFull(int fd, iovec* iov, int count) { ...@@ -162,6 +163,30 @@ ssize_t writevFull(int fd, iovec* iov, int count) {
ssize_t pwritevFull(int fd, iovec* iov, int count, off_t offset) { ssize_t pwritevFull(int fd, iovec* iov, int count, off_t offset) {
return wrapvFull(pwritev, fd, iov, count, offset); return wrapvFull(pwritev, fd, iov, count, offset);
} }
#else // _WIN32
// On Windows, the *vFull() functions wrap the simple read/pread/write/pwrite
// functions. While folly/portability/SysUio.cpp does define readv() and
// writev() implementations for Windows, these attempt to lock the file to
// provide atomicity. The *vFull() functions do not provide any atomicity
// guarantees, so we can avoid the locking logic.
ssize_t readvFull(int fd, iovec* iov, int count) {
return wrapvFull(read, fd, iov, count);
}
ssize_t preadvFull(int fd, iovec* iov, int count, off_t offset) {
return wrapvFull(pread, fd, iov, count, offset);
}
ssize_t writevFull(int fd, iovec* iov, int count) {
return wrapvFull(write, fd, iov, count);
}
ssize_t pwritevFull(int fd, iovec* iov, int count, off_t offset) {
return wrapvFull(pwrite, fd, iov, count, offset);
}
#endif // _WIN32
int writeFileAtomicNoThrow( int writeFileAtomicNoThrow(
StringPiece filename, StringPiece filename,
......
...@@ -72,14 +72,24 @@ ssize_t wrapFull(F f, int fd, void* buf, size_t count, Offset... offset) { ...@@ -72,14 +72,24 @@ ssize_t wrapFull(F f, int fd, void* buf, size_t count, Offset... offset) {
return totalBytes; return totalBytes;
} }
// Wrap call to readv/preadv/writev/pwritev(fd, iov, count, offset?) to // Retry reading/writing iovec objects.
// retry on incomplete reads / writes. // On POSIX platforms this wraps readv/preadv/writev/pwritev to
// retry on incomplete reads / writes. On Windows this wraps
// read/pread/write/pwrite. Note that pread and pwrite are not native calls on
// Windows and are provided by folly/portability/Unistd.cpp
template <class F, class... Offset> template <class F, class... Offset>
ssize_t wrapvFull(F f, int fd, iovec* iov, int count, Offset... offset) { ssize_t wrapvFull(F f, int fd, iovec* iov, int count, Offset... offset) {
ssize_t totalBytes = 0; ssize_t totalBytes = 0;
ssize_t r; ssize_t r;
do { do {
#ifndef _WIN32
r = f(fd, iov, std::min<int>(count, kIovMax), offset...); r = f(fd, iov, std::min<int>(count, kIovMax), offset...);
#else // _WIN32
// On Windows the caller will pass in just the simple
// read/write/pread/pwrite function, since the OS does not provide *v()
// versions.
r = f(fd, iov->iov_base, iov->iov_len, offset...);
#endif // _WIN32
if (r == -1) { if (r == -1) {
if (errno == EINTR) { if (errno == EINTR) {
continue; continue;
......
...@@ -93,7 +93,20 @@ ssize_t Reader::operator()(int /* fd */, void* buf, size_t count) { ...@@ -93,7 +93,20 @@ ssize_t Reader::operator()(int /* fd */, void* buf, size_t count) {
return n; return n;
} }
if (size_t(n) > count) { if (size_t(n) > count) {
#ifndef _WIN32
throw std::runtime_error("requested count too small"); throw std::runtime_error("requested count too small");
#else
// On Windows the code does not use readv(), so it will perform
// individual reads for each separate iovec element. These reads may arrive
// in smaller chunks than we were asked to return.
//
// Return the partial read data, and push the remaining size back onto the
// front of spec_ so we will return that on the next call.
auto remainder = n - count;
spec_.push_front(remainder);
offset_ -= remainder;
n = count;
#endif
} }
memcpy(buf, data_.data(), n); memcpy(buf, data_.data(), n);
data_.advance(n); data_.advance(n);
...@@ -256,7 +269,11 @@ TEST(FileUtilTest2, wrapv) { ...@@ -256,7 +269,11 @@ TEST(FileUtilTest2, wrapv) {
IovecBuffers buf(sizes); IovecBuffers buf(sizes);
ASSERT_EQ(sum, buf.size()); ASSERT_EQ(sum, buf.size());
auto iov = buf.iov(); auto iov = buf.iov();
#ifndef _WIN32
EXPECT_EQ(sum, wrapvFull(writev, tempFile.fd(), iov.data(), iov.size())); EXPECT_EQ(sum, wrapvFull(writev, tempFile.fd(), iov.data(), iov.size()));
#else
EXPECT_EQ(sum, wrapvFull(write, tempFile.fd(), iov.data(), iov.size()));
#endif
} }
TEST_F(FileUtilTest, preadv) { TEST_F(FileUtilTest, preadv) {
...@@ -273,6 +290,26 @@ TEST_F(FileUtilTest, preadv) { ...@@ -273,6 +290,26 @@ TEST_F(FileUtilTest, preadv) {
} }
} }
TEST_F(FileUtilTest, writeVStderr) {
// Test writing to stderr with writevFull(), and verify that it succeeds.
// Previously this would fail on Windows, as folly's writev() implementation
// would call lockf(), and this would hang and eventually fail for stderr.
std::vector<iovec> iov;
size_t totalSize = 0;
auto addBuf = [&](StringPiece str) {
iov.emplace_back();
iov.back().iov_base = const_cast<char*>(str.data());
iov.back().iov_len = str.size();
totalSize += str.size();
};
addBuf("this");
addBuf(" is ");
addBuf("a test\n ");
auto rc = writevFull(STDERR_FILENO, iov.data(), iov.size());
EXPECT_EQ(rc, totalSize);
}
TEST(String, readFile) { TEST(String, readFile) {
const TemporaryFile afileTemp, emptyFileTemp; const TemporaryFile afileTemp, emptyFileTemp;
auto afile = afileTemp.path().string(); auto afile = afileTemp.path().string();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment