Commit f6784e42 authored by Dan Melnic's avatar Dan Melnic Committed by Facebook GitHub Bot

Expose async read/write functionality

Summary: Expose async read/write functionality

Reviewed By: yfeldblum, kevin-vigor

Differential Revision: D21731597

fbshipit-source-id: 95a0af53c7b85e1bf1a2619586091b6633bc7b8a
parent 8f91f848
...@@ -308,16 +308,22 @@ size_t IoUringBackend::submitList( ...@@ -308,16 +308,22 @@ size_t IoUringBackend::submitList(
CHECK(sqe); // this should not happen CHECK(sqe); // this should not happen
auto* ev = entry->event_->getEvent(); auto* ev = entry->event_->getEvent();
if (ev) {
const auto& cb = entry->event_->getCallback(); const auto& cb = entry->event_->getCallback();
bool processed = false; bool processed = false;
switch (cb.type_) { switch (cb.type_) {
case EventCallback::Type::TYPE_NONE: case EventCallback::Type::TYPE_NONE:
processed = entry->processSubmit(sqe);
break; break;
case EventCallback::Type::TYPE_READ: case EventCallback::Type::TYPE_READ:
if (auto* iov = cb.readCb_->allocateData()) { if (auto* iov = cb.readCb_->allocateData()) {
processed = true; processed = true;
entry->prepRead( entry->prepRead(
sqe, ev->ev_fd, &iov->data_, (ev->ev_events & EV_PERSIST) != 0); sqe,
ev->ev_fd,
&iov->data_,
0,
(ev->ev_events & EV_PERSIST) != 0);
entry->cbData_.set(iov); entry->cbData_.set(iov);
} }
break; break;
...@@ -339,6 +345,9 @@ size_t IoUringBackend::submitList( ...@@ -339,6 +345,9 @@ size_t IoUringBackend::submitList(
getPollFlags(ev->ev_events), getPollFlags(ev->ev_events),
(ev->ev_events & EV_PERSIST) != 0); (ev->ev_events & EV_PERSIST) != 0);
} }
} else {
entry->processSubmit(sqe);
}
i++; i++;
if (ioCbs.empty()) { if (ioCbs.empty()) {
int num = submitBusyCheck(i, waitForEvents); int num = submitBusyCheck(i, waitForEvents);
...@@ -356,4 +365,73 @@ size_t IoUringBackend::submitList( ...@@ -356,4 +365,73 @@ size_t IoUringBackend::submitList(
return ret; return ret;
} }
void IoUringBackend::queueRead(
int fd,
void* buf,
unsigned int nbytes,
off_t offset,
FileOpCallback&& cb) {
struct iovec iov {
buf, nbytes
};
auto* iocb = new ReadIoSqe(this, fd, &iov, offset, std::move(cb));
iocb->backendCb_ = processReadWriteCB;
incNumIoCbInUse();
submitList_.push_back(*iocb);
numInsertedEvents_++;
}
void IoUringBackend::queueWrite(
int fd,
const void* buf,
unsigned int nbytes,
off_t offset,
FileOpCallback&& cb) {
struct iovec iov {
const_cast<void*>(buf), nbytes
};
auto* iocb = new WriteIoSqe(this, fd, &iov, offset, std::move(cb));
iocb->backendCb_ = processReadWriteCB;
incNumIoCbInUse();
submitList_.push_back(*iocb);
numInsertedEvents_++;
}
void IoUringBackend::queueReadv(
int fd,
Range<const struct iovec*> iovecs,
off_t offset,
FileOpCallback&& cb) {
auto* iocb = new ReadvIoSqe(this, fd, iovecs, offset, std::move(cb));
iocb->backendCb_ = processReadWriteCB;
incNumIoCbInUse();
submitList_.push_back(*iocb);
numInsertedEvents_++;
}
void IoUringBackend::queueWritev(
int fd,
Range<const struct iovec*> iovecs,
off_t offset,
FileOpCallback&& cb) {
auto* iocb = new WritevIoSqe(this, fd, iovecs, offset, std::move(cb));
iocb->backendCb_ = processReadWriteCB;
incNumIoCbInUse();
submitList_.push_back(*iocb);
numInsertedEvents_++;
}
void IoUringBackend::processReadWrite(IoCb* ioCb, int64_t res) noexcept {
auto* ioSqe = reinterpret_cast<ReadWriteIoSqe*>(ioCb);
// save the res
ioSqe->res_ = res;
activeEvents_.push_back(*ioCb);
numInsertedEvents_--;
}
} // namespace folly } // namespace folly
...@@ -20,7 +20,11 @@ extern "C" { ...@@ -20,7 +20,11 @@ extern "C" {
#include <liburing.h> #include <liburing.h>
} }
#include <folly/Function.h>
#include <folly/Range.h>
#include <folly/experimental/io/PollIoBackend.h> #include <folly/experimental/io/PollIoBackend.h>
#include <folly/small_vector.h>
#include <glog/logging.h> #include <glog/logging.h>
namespace folly { namespace folly {
...@@ -48,6 +52,37 @@ class IoUringBackend : public PollIoBackend { ...@@ -48,6 +52,37 @@ class IoUringBackend : public PollIoBackend {
return fdRegistry_.free(rec); return fdRegistry_.free(rec);
} }
// read/write file operation callback
// int param is the io_uring_cqe res field
// i.e. the result of the read/write operation
using FileOpCallback = folly::Function<void(int)>;
void queueRead(
int fd,
void* buf,
unsigned int nbytes,
off_t offset,
FileOpCallback&& cb);
void queueWrite(
int fd,
const void* buf,
unsigned int nbytes,
off_t offset,
FileOpCallback&& cb);
void queueReadv(
int fd,
Range<const struct iovec*> iovecs,
off_t offset,
FileOpCallback&& cb);
void queueWritev(
int fd,
Range<const struct iovec*> iovecs,
off_t offset,
FileOpCallback&& cb);
protected: protected:
struct FdRegistry { struct FdRegistry {
FdRegistry() = delete; FdRegistry() = delete;
...@@ -100,8 +135,12 @@ class IoUringBackend : public PollIoBackend { ...@@ -100,8 +135,12 @@ class IoUringBackend : public PollIoBackend {
::io_uring_sqe_set_data(sqe, this); ::io_uring_sqe_set_data(sqe, this);
} }
void prepRead(void* entry, int fd, const struct iovec* iov, bool registerFd) void prepRead(
override { void* entry,
int fd,
const struct iovec* iov,
off_t offset,
bool registerFd) override {
CHECK(entry); CHECK(entry);
struct io_uring_sqe* sqe = reinterpret_cast<struct io_uring_sqe*>(entry); struct io_uring_sqe* sqe = reinterpret_cast<struct io_uring_sqe*>(entry);
if (registerFd && !fdRecord_) { if (registerFd && !fdRecord_) {
...@@ -110,11 +149,32 @@ class IoUringBackend : public PollIoBackend { ...@@ -110,11 +149,32 @@ class IoUringBackend : public PollIoBackend {
if (fdRecord_) { if (fdRecord_) {
::io_uring_prep_read( ::io_uring_prep_read(
sqe, fdRecord_->idx_, iov->iov_base, iov->iov_len, 0 /*offset*/); sqe, fdRecord_->idx_, iov->iov_base, iov->iov_len, offset);
sqe->flags |= IOSQE_FIXED_FILE; sqe->flags |= IOSQE_FIXED_FILE;
} else { } else {
::io_uring_prep_read( ::io_uring_prep_read(sqe, fd, iov->iov_base, iov->iov_len, offset);
sqe, fd, iov->iov_base, iov->iov_len, 0 /*offset*/); }
::io_uring_sqe_set_data(sqe, this);
}
void prepWrite(
void* entry,
int fd,
const struct iovec* iov,
off_t offset,
bool registerFd) override {
CHECK(entry);
struct io_uring_sqe* sqe = reinterpret_cast<struct io_uring_sqe*>(entry);
if (registerFd && !fdRecord_) {
fdRecord_ = backend_->registerFd(fd);
}
if (fdRecord_) {
::io_uring_prep_write(
sqe, fdRecord_->idx_, iov->iov_base, iov->iov_len, offset);
sqe->flags |= IOSQE_FIXED_FILE;
} else {
::io_uring_prep_write(sqe, fd, iov->iov_base, iov->iov_len, offset);
} }
::io_uring_sqe_set_data(sqe, this); ::io_uring_sqe_set_data(sqe, this);
} }
...@@ -145,6 +205,100 @@ class IoUringBackend : public PollIoBackend { ...@@ -145,6 +205,100 @@ class IoUringBackend : public PollIoBackend {
} }
}; };
struct ReadWriteIoSqe : public IoSqe {
ReadWriteIoSqe(
PollIoBackend* backend,
int fd,
const struct iovec* iov,
off_t offset,
FileOpCallback&& cb)
: IoSqe(backend, false),
fd_(fd),
iov_(iov, iov + 1),
offset_(offset),
cb_(std::move(cb)) {}
ReadWriteIoSqe(
PollIoBackend* backend,
int fd,
Range<const struct iovec*> iov,
off_t offset,
FileOpCallback&& cb)
: IoSqe(backend, false),
fd_(fd),
iov_(iov),
offset_(offset),
cb_(std::move(cb)) {}
~ReadWriteIoSqe() override = default;
void processActive() override {
cb_(res_);
}
int fd_{-1};
int res_{-1};
static constexpr size_t kNumInlineIoVec = 4;
folly::small_vector<struct iovec> iov_;
off_t offset_;
FileOpCallback cb_;
};
struct ReadIoSqe : public ReadWriteIoSqe {
using ReadWriteIoSqe::ReadWriteIoSqe;
~ReadIoSqe() override = default;
bool processSubmit(void* entry) override {
prepRead(entry, fd_, iov_.data(), offset_, false);
return true;
}
};
struct WriteIoSqe : public ReadWriteIoSqe {
using ReadWriteIoSqe::ReadWriteIoSqe;
~WriteIoSqe() override = default;
bool processSubmit(void* entry) override {
prepWrite(entry, fd_, iov_.data(), offset_, false);
return true;
}
};
struct ReadvIoSqe : public ReadWriteIoSqe {
using ReadWriteIoSqe::ReadWriteIoSqe;
~ReadvIoSqe() override = default;
bool processSubmit(void* entry) override {
struct io_uring_sqe* sqe = reinterpret_cast<struct io_uring_sqe*>(entry);
::io_uring_prep_readv(sqe, fd_, iov_.data(), iov_.size(), offset_);
::io_uring_sqe_set_data(sqe, this);
return true;
}
};
struct WritevIoSqe : public ReadWriteIoSqe {
using ReadWriteIoSqe::ReadWriteIoSqe;
~WritevIoSqe() override = default;
bool processSubmit(void* entry) override {
struct io_uring_sqe* sqe = reinterpret_cast<struct io_uring_sqe*>(entry);
::io_uring_prep_writev(sqe, fd_, iov_.data(), iov_.size(), offset_);
::io_uring_sqe_set_data(sqe, this);
return true;
}
};
void processReadWrite(IoCb* ioCb, int64_t res) noexcept;
static void
processReadWriteCB(PollIoBackend* backend, IoCb* ioCb, int64_t res) {
static_cast<IoUringBackend*>(backend)->processReadWrite(ioCb, res);
}
PollIoBackend::IoCb* allocNewIoCb(const EventCallback& /*cb*/) override { PollIoBackend::IoCb* allocNewIoCb(const EventCallback& /*cb*/) override {
auto* ret = new IoSqe(this, false); auto* ret = new IoSqe(this, false);
ret->backendCb_ = PollIoBackend::processPollIoCb; ret->backendCb_ = PollIoBackend::processPollIoCb;
......
...@@ -353,6 +353,7 @@ PollIoBackend::IoCb* PollIoBackend::allocIoCb(const EventCallback& cb) { ...@@ -353,6 +353,7 @@ PollIoBackend::IoCb* PollIoBackend::allocIoCb(const EventCallback& cb) {
} }
void PollIoBackend::releaseIoCb(PollIoBackend::IoCb* aioIoCb) { void PollIoBackend::releaseIoCb(PollIoBackend::IoCb* aioIoCb) {
CHECK_GT(numIoCbInUse_, 0);
numIoCbInUse_--; numIoCbInUse_--;
aioIoCb->cbData_.releaseData(); aioIoCb->cbData_.releaseData();
// unregister the file descriptor record // unregister the file descriptor record
...@@ -430,6 +431,8 @@ size_t PollIoBackend::processActiveEvents() { ...@@ -430,6 +431,8 @@ size_t PollIoBackend::processActiveEvents() {
eb_event_modify_inserted(*event, ioCb); eb_event_modify_inserted(*event, ioCb);
} }
ioCb->useCount_--; ioCb->useCount_--;
} else {
ioCb->processActive();
} }
if (release) { if (release) {
releaseIoCb(ioCb); releaseIoCb(ioCb);
......
...@@ -110,6 +110,12 @@ class PollIoBackend : public EventBaseBackendBase { ...@@ -110,6 +110,12 @@ class PollIoBackend : public EventBaseBackendBase {
: backend_(backend), poolAlloc_(poolAlloc) {} : backend_(backend), poolAlloc_(poolAlloc) {}
virtual ~IoCb() = default; virtual ~IoCb() = default;
virtual bool processSubmit(void* /*entry*/) {
return false;
}
virtual void processActive() {}
PollIoBackend* backend_; PollIoBackend* backend_;
BackendCb* backendCb_{nullptr}; BackendCb* backendCb_{nullptr};
const bool poolAlloc_; const bool poolAlloc_;
...@@ -134,6 +140,14 @@ class PollIoBackend : public EventBaseBackendBase { ...@@ -134,6 +140,14 @@ class PollIoBackend : public EventBaseBackendBase {
void* /*entry*/, void* /*entry*/,
int /*fd*/, int /*fd*/,
const struct iovec* /*iov*/, const struct iovec* /*iov*/,
off_t /*offset*/,
bool /*registerFd*/) {}
virtual void prepWrite(
void* /*entry*/,
int /*fd*/,
const struct iovec* /*iov*/,
off_t /*offset*/,
bool /*registerFd*/) {} bool /*registerFd*/) {}
virtual void prepRecvmsg( virtual void prepRecvmsg(
...@@ -343,6 +357,9 @@ class PollIoBackend : public EventBaseBackendBase { ...@@ -343,6 +357,9 @@ class PollIoBackend : public EventBaseBackendBase {
IoCb* FOLLY_NULLABLE allocIoCb(const EventCallback& cb); IoCb* FOLLY_NULLABLE allocIoCb(const EventCallback& cb);
void releaseIoCb(IoCb* aioIoCb); void releaseIoCb(IoCb* aioIoCb);
void incNumIoCbInUse() {
numIoCbInUse_++;
}
virtual IoCb* allocNewIoCb(const EventCallback& cb) = 0; virtual IoCb* allocNewIoCb(const EventCallback& cb) = 0;
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
namespace folly { namespace folly {
namespace test { namespace test {
namespace async_base_test_lib_detail {
void TestUtil::waitUntilReadable(int fd) { void TestUtil::waitUntilReadable(int fd) {
pollfd pfd; pollfd pfd;
pfd.fd = fd; pfd.fd = fd;
...@@ -44,51 +45,11 @@ folly::Range<folly::AsyncBase::Op**> TestUtil::readerWait( ...@@ -44,51 +45,11 @@ folly::Range<folly::AsyncBase::Op**> TestUtil::readerWait(
TestUtil::ManagedBuffer TestUtil::allocateAligned(size_t size) { TestUtil::ManagedBuffer TestUtil::allocateAligned(size_t size) {
void* buf; void* buf;
int rc = posix_memalign(&buf, kAlign, size); int rc = posix_memalign(&buf, kODirectAlign, size);
CHECK_EQ(rc, 0) << folly::errnoStr(rc); CHECK_EQ(rc, 0) << folly::errnoStr(rc);
return ManagedBuffer(reinterpret_cast<char*>(buf), free); return ManagedBuffer(reinterpret_cast<char*>(buf), free);
} }
TemporaryFile::TemporaryFile(size_t size) } // namespace async_base_test_lib_detail
: path_(folly::fs::temp_directory_path() / folly::fs::unique_path()) {
CHECK_EQ(size % sizeof(uint32_t), 0);
size /= sizeof(uint32_t);
const uint32_t seed = 42;
std::mt19937 rnd(seed);
const size_t bufferSize = 1U << 16;
uint32_t buffer[bufferSize];
FILE* fp = ::fopen(path_.c_str(), "wb");
PCHECK(fp != nullptr);
while (size) {
size_t n = std::min(size, bufferSize);
for (size_t i = 0; i < n; ++i) {
buffer[i] = rnd();
}
size_t written = ::fwrite(buffer, sizeof(uint32_t), n, fp);
PCHECK(written == n);
size -= written;
}
// make sure the buffers are flushed
int ret = ::fflush(fp);
PCHECK(ret == 0);
ret = ::fdatasync(::fileno(fp));
PCHECK(ret == 0);
PCHECK(::fclose(fp) == 0);
}
TemporaryFile::~TemporaryFile() {
try {
folly::fs::remove(path_);
} catch (const folly::fs::filesystem_error& e) {
LOG(ERROR) << "fs::remove: " << folly::exceptionStr(e);
}
}
TemporaryFile& TemporaryFile::getTempFile() {
static TemporaryFile sTempFile(6 << 20); // 6MiB
return sTempFile;
}
} // namespace test } // namespace test
} // namespace folly } // namespace folly
...@@ -36,11 +36,14 @@ ...@@ -36,11 +36,14 @@
#include <folly/test/TestUtils.h> #include <folly/test/TestUtils.h>
#include <folly/experimental/io/AsyncBase.h> #include <folly/experimental/io/AsyncBase.h>
#include <folly/experimental/io/test/IoTestTempFileUtil.h>
namespace folly { namespace folly {
namespace test { namespace test {
namespace async_base_test_lib_detail {
constexpr size_t kAlign = 4096; // align reads to 4096 B (for O_DIRECT) constexpr size_t kODirectAlign = 4096; // align reads to 4096 B (for O_DIRECT)
constexpr size_t kDefaultFileSize = 6 << 20; // 6MiB
struct TestSpec { struct TestSpec {
off_t start; off_t start;
...@@ -55,31 +58,14 @@ struct TestUtil { ...@@ -55,31 +58,14 @@ struct TestUtil {
static ManagedBuffer allocateAligned(size_t size); static ManagedBuffer allocateAligned(size_t size);
}; };
// Temporary file that is NOT kept open but is deleted on exit.
// Generate random-looking but reproduceable data.
class TemporaryFile {
public:
explicit TemporaryFile(size_t size);
~TemporaryFile();
const folly::fs::path path() const {
return path_;
}
static TemporaryFile& getTempFile();
private:
folly::fs::path path_;
};
template <typename TAsync> template <typename TAsync>
void testReadsSerially( void testReadsSerially(
const std::vector<TestSpec>& specs, const std::vector<TestSpec>& specs,
folly::AsyncBase::PollMode pollMode) { folly::AsyncBase::PollMode pollMode) {
TAsync aioReader(1, pollMode); TAsync aioReader(1, pollMode);
typename TAsync::Op op; typename TAsync::Op op;
int fd = auto tempFile = folly::test::TempFileUtil::getTempFile(kDefaultFileSize);
::open(TemporaryFile::getTempFile().path().c_str(), O_DIRECT | O_RDONLY); int fd = ::open(tempFile.path().c_str(), O_DIRECT | O_RDONLY);
SKIP_IF(fd == -1) << "Tempfile can't be opened with O_DIRECT: " SKIP_IF(fd == -1) << "Tempfile can't be opened with O_DIRECT: "
<< folly::errnoStr(errno); << folly::errnoStr(errno);
SCOPE_EXIT { SCOPE_EXIT {
...@@ -92,7 +78,8 @@ void testReadsSerially( ...@@ -92,7 +78,8 @@ void testReadsSerially(
aioReader.submit(&op); aioReader.submit(&op);
EXPECT_EQ((i + 1), aioReader.totalSubmits()); EXPECT_EQ((i + 1), aioReader.totalSubmits());
EXPECT_EQ(aioReader.pending(), 1); EXPECT_EQ(aioReader.pending(), 1);
auto ops = test::TestUtil::readerWait(&aioReader); auto ops =
test::async_base_test_lib_detail::TestUtil::readerWait(&aioReader);
EXPECT_EQ(1, ops.size()); EXPECT_EQ(1, ops.size());
EXPECT_TRUE(ops[0] == &op); EXPECT_TRUE(ops[0] == &op);
EXPECT_EQ(aioReader.pending(), 0); EXPECT_EQ(aioReader.pending(), 0);
...@@ -115,8 +102,8 @@ void testReadsParallel( ...@@ -115,8 +102,8 @@ void testReadsParallel(
std::vector<TestUtil::ManagedBuffer> bufs; std::vector<TestUtil::ManagedBuffer> bufs;
bufs.reserve(specs.size()); bufs.reserve(specs.size());
int fd = auto tempFile = folly::test::TempFileUtil::getTempFile(kDefaultFileSize);
::open(TemporaryFile::getTempFile().path().c_str(), O_DIRECT | O_RDONLY); int fd = ::open(tempFile.path().c_str(), O_DIRECT | O_RDONLY);
SKIP_IF(fd == -1) << "Tempfile can't be opened with O_DIRECT: " SKIP_IF(fd == -1) << "Tempfile can't be opened with O_DIRECT: "
<< folly::errnoStr(errno); << folly::errnoStr(errno);
SCOPE_EXIT { SCOPE_EXIT {
...@@ -149,7 +136,8 @@ void testReadsParallel( ...@@ -149,7 +136,8 @@ void testReadsParallel(
size_t remaining = specs.size(); size_t remaining = specs.size();
while (remaining != 0) { while (remaining != 0) {
EXPECT_EQ(remaining, aioReader.pending()); EXPECT_EQ(remaining, aioReader.pending());
auto completed = test::TestUtil::readerWait(&aioReader); auto completed =
test::async_base_test_lib_detail::TestUtil::readerWait(&aioReader);
size_t nrRead = completed.size(); size_t nrRead = completed.size();
EXPECT_NE(nrRead, 0); EXPECT_NE(nrRead, 0);
remaining -= nrRead; remaining -= nrRead;
...@@ -187,8 +175,8 @@ void testReadsQueued( ...@@ -187,8 +175,8 @@ void testReadsQueued(
uintptr_t sizeOf = sizeof(typename TAsync::Op); uintptr_t sizeOf = sizeof(typename TAsync::Op);
std::vector<TestUtil::ManagedBuffer> bufs; std::vector<TestUtil::ManagedBuffer> bufs;
int fd = auto tempFile = folly::test::TempFileUtil::getTempFile(kDefaultFileSize);
::open(TemporaryFile::getTempFile().path().c_str(), O_DIRECT | O_RDONLY); int fd = ::open(tempFile.path().c_str(), O_DIRECT | O_RDONLY);
SKIP_IF(fd == -1) << "Tempfile can't be opened with O_DIRECT: " SKIP_IF(fd == -1) << "Tempfile can't be opened with O_DIRECT: "
<< folly::errnoStr(errno); << folly::errnoStr(errno);
SCOPE_EXIT { SCOPE_EXIT {
...@@ -210,7 +198,8 @@ void testReadsQueued( ...@@ -210,7 +198,8 @@ void testReadsQueued(
EXPECT_EQ(remaining, aioReader.pending()); EXPECT_EQ(remaining, aioReader.pending());
EXPECT_EQ(0, aioQueue.queued()); EXPECT_EQ(0, aioQueue.queued());
} }
auto completed = test::TestUtil::readerWait(&aioReader); auto completed =
test::async_base_test_lib_detail::TestUtil::readerWait(&aioReader);
size_t nrRead = completed.size(); size_t nrRead = completed.size();
EXPECT_NE(nrRead, 0); EXPECT_NE(nrRead, 0);
remaining -= nrRead; remaining -= nrRead;
...@@ -251,111 +240,143 @@ class AsyncTest : public ::testing::Test {}; ...@@ -251,111 +240,143 @@ class AsyncTest : public ::testing::Test {};
TYPED_TEST_CASE_P(AsyncTest); TYPED_TEST_CASE_P(AsyncTest);
TYPED_TEST_P(AsyncTest, ZeroAsyncDataNotPollable) { TYPED_TEST_P(AsyncTest, ZeroAsyncDataNotPollable) {
test::testReads<TypeParam>({{0, 0}}, folly::AsyncBase::NOT_POLLABLE); test::async_base_test_lib_detail::testReads<TypeParam>(
{{0, 0}}, folly::AsyncBase::NOT_POLLABLE);
} }
TYPED_TEST_P(AsyncTest, ZeroAsyncDataPollable) { TYPED_TEST_P(AsyncTest, ZeroAsyncDataPollable) {
test::testReads<TypeParam>({{0, 0}}, folly::AsyncBase::POLLABLE); test::async_base_test_lib_detail::testReads<TypeParam>(
{{0, 0}}, folly::AsyncBase::POLLABLE);
} }
TYPED_TEST_P(AsyncTest, SingleAsyncDataNotPollable) { TYPED_TEST_P(AsyncTest, SingleAsyncDataNotPollable) {
test::testReads<TypeParam>( test::async_base_test_lib_detail::testReads<TypeParam>(
{{0, test::kAlign}}, folly::AsyncBase::NOT_POLLABLE); {{0, test::async_base_test_lib_detail::kODirectAlign}},
test::testReads<TypeParam>( folly::AsyncBase::NOT_POLLABLE);
{{0, test::kAlign}}, folly::AsyncBase::NOT_POLLABLE); test::async_base_test_lib_detail::testReads<TypeParam>(
{{0, test::async_base_test_lib_detail::kODirectAlign}},
folly::AsyncBase::NOT_POLLABLE);
} }
TYPED_TEST_P(AsyncTest, SingleAsyncDataPollable) { TYPED_TEST_P(AsyncTest, SingleAsyncDataPollable) {
test::testReads<TypeParam>({{0, test::kAlign}}, folly::AsyncBase::POLLABLE); test::async_base_test_lib_detail::testReads<TypeParam>(
test::testReads<TypeParam>({{0, test::kAlign}}, folly::AsyncBase::POLLABLE); {{0, test::async_base_test_lib_detail::kODirectAlign}},
folly::AsyncBase::POLLABLE);
test::async_base_test_lib_detail::testReads<TypeParam>(
{{0, test::async_base_test_lib_detail::kODirectAlign}},
folly::AsyncBase::POLLABLE);
} }
TYPED_TEST_P(AsyncTest, MultipleAsyncDataNotPollable) { TYPED_TEST_P(AsyncTest, MultipleAsyncDataNotPollable) {
test::testReads<TypeParam>( test::async_base_test_lib_detail::testReads<TypeParam>(
{{test::kAlign, 2 * test::kAlign}, {{test::async_base_test_lib_detail::kODirectAlign,
{test::kAlign, 2 * test::kAlign}, 2 * test::async_base_test_lib_detail::kODirectAlign},
{test::kAlign, 4 * test::kAlign}}, {test::async_base_test_lib_detail::kODirectAlign,
2 * test::async_base_test_lib_detail::kODirectAlign},
{test::async_base_test_lib_detail::kODirectAlign,
4 * test::async_base_test_lib_detail::kODirectAlign}},
folly::AsyncBase::NOT_POLLABLE); folly::AsyncBase::NOT_POLLABLE);
test::testReads<TypeParam>( test::async_base_test_lib_detail::testReads<TypeParam>(
{{test::kAlign, 2 * test::kAlign}, {{test::async_base_test_lib_detail::kODirectAlign,
{test::kAlign, 2 * test::kAlign}, 2 * test::async_base_test_lib_detail::kODirectAlign},
{test::kAlign, 4 * test::kAlign}}, {test::async_base_test_lib_detail::kODirectAlign,
2 * test::async_base_test_lib_detail::kODirectAlign},
{test::async_base_test_lib_detail::kODirectAlign,
4 * test::async_base_test_lib_detail::kODirectAlign}},
folly::AsyncBase::NOT_POLLABLE); folly::AsyncBase::NOT_POLLABLE);
test::testReads<TypeParam>( test::async_base_test_lib_detail::testReads<TypeParam>(
{{0, 5 * 1024 * 1024}, {test::kAlign, 5 * 1024 * 1024}}, {{0, 5 * 1024 * 1024},
{test::async_base_test_lib_detail::kODirectAlign, 5 * 1024 * 1024}},
folly::AsyncBase::NOT_POLLABLE); folly::AsyncBase::NOT_POLLABLE);
test::testReads<TypeParam>( test::async_base_test_lib_detail::testReads<TypeParam>(
{ {
{test::kAlign, 0}, {test::async_base_test_lib_detail::kODirectAlign, 0},
{test::kAlign, test::kAlign}, {test::async_base_test_lib_detail::kODirectAlign,
{test::kAlign, 2 * test::kAlign}, test::async_base_test_lib_detail::kODirectAlign},
{test::kAlign, 20 * test::kAlign}, {test::async_base_test_lib_detail::kODirectAlign,
{test::kAlign, 1024 * 1024}, 2 * test::async_base_test_lib_detail::kODirectAlign},
{test::async_base_test_lib_detail::kODirectAlign,
20 * test::async_base_test_lib_detail::kODirectAlign},
{test::async_base_test_lib_detail::kODirectAlign, 1024 * 1024},
}, },
folly::AsyncBase::NOT_POLLABLE); folly::AsyncBase::NOT_POLLABLE);
} }
TYPED_TEST_P(AsyncTest, MultipleAsyncDataPollable) { TYPED_TEST_P(AsyncTest, MultipleAsyncDataPollable) {
test::testReads<TypeParam>( test::async_base_test_lib_detail::testReads<TypeParam>(
{{test::kAlign, 2 * test::kAlign}, {{test::async_base_test_lib_detail::kODirectAlign,
{test::kAlign, 2 * test::kAlign}, 2 * test::async_base_test_lib_detail::kODirectAlign},
{test::kAlign, 4 * test::kAlign}}, {test::async_base_test_lib_detail::kODirectAlign,
2 * test::async_base_test_lib_detail::kODirectAlign},
{test::async_base_test_lib_detail::kODirectAlign,
4 * test::async_base_test_lib_detail::kODirectAlign}},
folly::AsyncBase::POLLABLE); folly::AsyncBase::POLLABLE);
test::testReads<TypeParam>( test::async_base_test_lib_detail::testReads<TypeParam>(
{{test::kAlign, 2 * test::kAlign}, {{test::async_base_test_lib_detail::kODirectAlign,
{test::kAlign, 2 * test::kAlign}, 2 * test::async_base_test_lib_detail::kODirectAlign},
{test::kAlign, 4 * test::kAlign}}, {test::async_base_test_lib_detail::kODirectAlign,
2 * test::async_base_test_lib_detail::kODirectAlign},
{test::async_base_test_lib_detail::kODirectAlign,
4 * test::async_base_test_lib_detail::kODirectAlign}},
folly::AsyncBase::POLLABLE); folly::AsyncBase::POLLABLE);
test::testReads<TypeParam>( test::async_base_test_lib_detail::testReads<TypeParam>(
{{0, 5 * 1024 * 1024}, {test::kAlign, 5 * 1024 * 1024}}, {{0, 5 * 1024 * 1024},
{test::async_base_test_lib_detail::kODirectAlign, 5 * 1024 * 1024}},
folly::AsyncBase::NOT_POLLABLE); folly::AsyncBase::NOT_POLLABLE);
test::testReads<TypeParam>( test::async_base_test_lib_detail::testReads<TypeParam>(
{ {
{test::kAlign, 0}, {test::async_base_test_lib_detail::kODirectAlign, 0},
{test::kAlign, test::kAlign}, {test::async_base_test_lib_detail::kODirectAlign,
{test::kAlign, 2 * test::kAlign}, test::async_base_test_lib_detail::kODirectAlign},
{test::kAlign, 20 * test::kAlign}, {test::async_base_test_lib_detail::kODirectAlign,
{test::kAlign, 1024 * 1024}, 2 * test::async_base_test_lib_detail::kODirectAlign},
{test::async_base_test_lib_detail::kODirectAlign,
20 * test::async_base_test_lib_detail::kODirectAlign},
{test::async_base_test_lib_detail::kODirectAlign, 1024 * 1024},
}, },
folly::AsyncBase::NOT_POLLABLE); folly::AsyncBase::NOT_POLLABLE);
} }
TYPED_TEST_P(AsyncTest, ManyAsyncDataNotPollable) { TYPED_TEST_P(AsyncTest, ManyAsyncDataNotPollable) {
{ {
std::vector<test::TestSpec> v; std::vector<test::async_base_test_lib_detail::TestSpec> v;
for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
v.push_back({off_t(test::kAlign * i), test::kAlign}); v.push_back({off_t(test::async_base_test_lib_detail::kODirectAlign * i),
test::async_base_test_lib_detail::kODirectAlign});
} }
test::testReads<TypeParam>(v, folly::AsyncBase::NOT_POLLABLE); test::async_base_test_lib_detail::testReads<TypeParam>(
v, folly::AsyncBase::NOT_POLLABLE);
} }
} }
TYPED_TEST_P(AsyncTest, ManyAsyncDataPollable) { TYPED_TEST_P(AsyncTest, ManyAsyncDataPollable) {
{ {
std::vector<test::TestSpec> v; std::vector<test::async_base_test_lib_detail::TestSpec> v;
for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
v.push_back({off_t(test::kAlign * i), test::kAlign}); v.push_back({off_t(test::async_base_test_lib_detail::kODirectAlign * i),
test::async_base_test_lib_detail::kODirectAlign});
} }
test::testReads<TypeParam>(v, folly::AsyncBase::POLLABLE); test::async_base_test_lib_detail::testReads<TypeParam>(
v, folly::AsyncBase::POLLABLE);
} }
} }
TYPED_TEST_P(AsyncTest, NonBlockingWait) { TYPED_TEST_P(AsyncTest, NonBlockingWait) {
TypeParam aioReader(1, folly::AsyncBase::NOT_POLLABLE); TypeParam aioReader(1, folly::AsyncBase::NOT_POLLABLE);
typename TypeParam::Op op; typename TypeParam::Op op;
int fd = ::open( auto tempFile = folly::test::TempFileUtil::getTempFile(kDefaultFileSize);
test::TemporaryFile::getTempFile().path().c_str(), O_DIRECT | O_RDONLY); int fd = ::open(tempFile.path().c_str(), O_DIRECT | O_RDONLY);
SKIP_IF(fd == -1) << "Tempfile can't be opened with O_DIRECT: " SKIP_IF(fd == -1) << "Tempfile can't be opened with O_DIRECT: "
<< folly::errnoStr(errno); << folly::errnoStr(errno);
SCOPE_EXIT { SCOPE_EXIT {
::close(fd); ::close(fd);
}; };
size_t size = 2 * test::kAlign; size_t size = 2 * test::async_base_test_lib_detail::kODirectAlign;
auto buf = test::TestUtil::allocateAligned(size); auto buf = test::async_base_test_lib_detail::TestUtil::allocateAligned(size);
op.pread(fd, buf.get(), size, 0); op.pread(fd, buf.get(), size, 0);
aioReader.submit(&op); aioReader.submit(&op);
EXPECT_EQ(aioReader.pending(), 1); EXPECT_EQ(aioReader.pending(), 1);
...@@ -380,8 +401,8 @@ TYPED_TEST_P(AsyncTest, Cancel) { ...@@ -380,8 +401,8 @@ TYPED_TEST_P(AsyncTest, Cancel) {
TypeParam aioReader( TypeParam aioReader(
kNumOpsBatch1 + kNumOpsBatch2, folly::AsyncBase::NOT_POLLABLE); kNumOpsBatch1 + kNumOpsBatch2, folly::AsyncBase::NOT_POLLABLE);
int fd = ::open( auto tempFile = folly::test::TempFileUtil::getTempFile(kDefaultFileSize);
test::TemporaryFile::getTempFile().path().c_str(), O_DIRECT | O_RDONLY); int fd = ::open(tempFile.path().c_str(), O_DIRECT | O_RDONLY);
SKIP_IF(fd == -1) << "Tempfile can't be opened with O_DIRECT: " SKIP_IF(fd == -1) << "Tempfile can't be opened with O_DIRECT: "
<< folly::errnoStr(errno); << folly::errnoStr(errno);
SCOPE_EXIT { SCOPE_EXIT {
...@@ -391,11 +412,12 @@ TYPED_TEST_P(AsyncTest, Cancel) { ...@@ -391,11 +412,12 @@ TYPED_TEST_P(AsyncTest, Cancel) {
size_t completed = 0; size_t completed = 0;
std::vector<std::unique_ptr<folly::AsyncBase::Op>> ops; std::vector<std::unique_ptr<folly::AsyncBase::Op>> ops;
std::vector<test::TestUtil::ManagedBuffer> bufs; std::vector<test::async_base_test_lib_detail::TestUtil::ManagedBuffer> bufs;
const auto schedule = [&](size_t n) { const auto schedule = [&](size_t n) {
for (size_t i = 0; i < n; ++i) { for (size_t i = 0; i < n; ++i) {
const size_t size = 2 * test::kAlign; const size_t size = 2 * test::async_base_test_lib_detail::kODirectAlign;
bufs.push_back(test::TestUtil::allocateAligned(size)); bufs.push_back(
test::async_base_test_lib_detail::TestUtil::allocateAligned(size));
ops.push_back(std::make_unique<typename TypeParam::Op>()); ops.push_back(std::make_unique<typename TypeParam::Op>());
auto& op = *ops.back(); auto& op = *ops.back();
...@@ -452,5 +474,6 @@ REGISTER_TYPED_TEST_CASE_P( ...@@ -452,5 +474,6 @@ REGISTER_TYPED_TEST_CASE_P(
NonBlockingWait, NonBlockingWait,
Cancel); Cancel);
} // namespace async_base_test_lib_detail
} // namespace test } // namespace test
} // namespace folly } // namespace folly
...@@ -21,6 +21,8 @@ using folly::AsyncIO; ...@@ -21,6 +21,8 @@ using folly::AsyncIO;
namespace folly { namespace folly {
namespace test { namespace test {
namespace async_base_test_lib_detail {
INSTANTIATE_TYPED_TEST_CASE_P(AsyncTest, AsyncTest, AsyncIO); INSTANTIATE_TYPED_TEST_CASE_P(AsyncTest, AsyncTest, AsyncIO);
} // namespace async_base_test_lib_detail
} // namespace test } // namespace test
} // namespace folly } // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/experimental/io/test/IoTestTempFileUtil.h>
#include <folly/FileUtil.h>
#include <folly/String.h>
#include <random>
namespace folly {
namespace test {
TemporaryFile TempFileUtil::getTempFile(size_t size) {
CHECK_EQ(size % sizeof(uint32_t), 0);
size /= sizeof(uint32_t);
TemporaryFile tmpFile;
int fd = tmpFile.fd();
CHECK_GE(fd, 0);
// fill the file the file with random data
const uint32_t seed = 42;
std::mt19937 rnd(seed);
constexpr size_t bufferSize = 1U << 16;
uint32_t buffer[bufferSize];
while (size) {
size_t n = std::min(size, bufferSize);
for (size_t i = 0; i < n; ++i) {
buffer[i] = rnd();
}
size_t written = folly::writeFull(fd, buffer, sizeof(uint32_t) * n);
CHECK_EQ(written, sizeof(uint32_t) * n);
size -= n;
}
CHECK_EQ(::fdatasync(fd), 0);
// the file was opened with O_EXCL so we need to close to be able
// to open it again
tmpFile.close();
return tmpFile;
}
} // namespace test
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <glog/logging.h>
#include <folly/experimental/TestUtil.h>
#include <folly/experimental/io/FsUtil.h>
namespace folly {
namespace test {
class TempFileUtil {
public:
// Returns a temporary file that is NOT kept open
// but is deleted on destruction
// Generate random-looking but reproduceable data.
static TemporaryFile getTempFile(size_t size);
private:
TempFileUtil() = delete;
};
} // namespace test
} // namespace folly
...@@ -15,9 +15,12 @@ ...@@ -15,9 +15,12 @@
*/ */
#include <sys/eventfd.h> #include <sys/eventfd.h>
#include <numeric>
#include <folly/FileUtil.h> #include <folly/FileUtil.h>
#include <folly/String.h>
#include <folly/experimental/io/IoUringBackend.h> #include <folly/experimental/io/IoUringBackend.h>
#include <folly/experimental/io/test/IoTestTempFileUtil.h>
#include <folly/init/Init.h> #include <folly/init/Init.h>
#include <folly/io/async/AsyncUDPServerSocket.h> #include <folly/io/async/AsyncUDPServerSocket.h>
#include <folly/io/async/AsyncUDPSocket.h> #include <folly/io/async/AsyncUDPSocket.h>
...@@ -532,6 +535,184 @@ TEST(IoUringBackend, RegisteredFds) { ...@@ -532,6 +535,184 @@ TEST(IoUringBackend, RegisteredFds) {
} }
} }
TEST(IoUringBackend, FileReadWrite) {
static constexpr size_t kBackendCapacity = 2048;
static constexpr size_t kBackendMaxSubmit = 32;
static constexpr size_t kBackendMaxGet = 32;
std::unique_ptr<folly::IoUringBackend> backend;
try {
folly::PollIoBackend::Options options;
options.setCapacity(kBackendCapacity)
.setMaxSubmit(kBackendMaxSubmit)
.setMaxGet(kBackendMaxGet)
.setUseRegisteredFds(false);
backend = std::make_unique<folly::IoUringBackend>(options);
} catch (const folly::IoUringBackend::NotAvailable&) {
}
SKIP_IF(!backend) << "Backend not available";
static constexpr size_t kNumBlocks = 512;
static constexpr size_t kBlockSize = 4096;
static constexpr size_t kFileSize = kNumBlocks * kBlockSize;
auto tempFile = folly::test::TempFileUtil::getTempFile(kFileSize);
int fd = ::open(tempFile.path().c_str(), O_DIRECT | O_RDWR);
SKIP_IF(fd == -1) << "Tempfile can't be opened with O_DIRECT: "
<< folly::errnoStr(errno);
SCOPE_EXIT {
::close(fd);
};
folly::EventBase evb(std::move(backend));
auto* backendPtr = dynamic_cast<folly::IoUringBackend*>(evb.getBackend());
CHECK(!!backendPtr);
size_t num = 0;
std::string writeData(kBlockSize, 'A'), readData(kBlockSize, 'Z');
std::vector<std::string> writeDataVec(kNumBlocks, writeData),
readDataVec(kNumBlocks, readData);
CHECK_NE(readData, writeData);
for (size_t i = 0; i < kNumBlocks; i++) {
folly::IoUringBackend::FileOpCallback writeCb = [&, i](int res) {
CHECK_EQ(res, writeDataVec[i].size());
folly::IoUringBackend::FileOpCallback readCb = [&, i](int res) {
CHECK_EQ(res, readDataVec[i].size());
CHECK_EQ(readDataVec[i], writeDataVec[i]);
++num;
};
backendPtr->queueRead(
fd,
readDataVec[i].data(),
readDataVec[i].size(),
i * kBlockSize,
std::move(readCb));
};
backendPtr->queueWrite(
fd,
writeDataVec[i].data(),
writeDataVec[i].size(),
i * kBlockSize,
std::move(writeCb));
}
evb.loop();
EXPECT_EQ(num, kNumBlocks);
}
TEST(IoUringBackend, FileReadvWritev) {
static constexpr size_t kBackendCapacity = 2048;
static constexpr size_t kBackendMaxSubmit = 32;
static constexpr size_t kBackendMaxGet = 32;
std::unique_ptr<folly::IoUringBackend> backend;
try {
folly::PollIoBackend::Options options;
options.setCapacity(kBackendCapacity)
.setMaxSubmit(kBackendMaxSubmit)
.setMaxGet(kBackendMaxGet)
.setUseRegisteredFds(false);
backend = std::make_unique<folly::IoUringBackend>(options);
} catch (const folly::IoUringBackend::NotAvailable&) {
}
SKIP_IF(!backend) << "Backend not available";
static constexpr size_t kNumBlocks = 512;
static constexpr size_t kNumIov = 4;
static constexpr size_t kIovSize = 1024;
static constexpr size_t kBlockSize = kNumIov * kIovSize;
static constexpr size_t kFileSize = kNumBlocks * kBlockSize;
auto tempFile = folly::test::TempFileUtil::getTempFile(kFileSize);
int fd = ::open(tempFile.path().c_str(), O_DIRECT | O_RDWR);
SKIP_IF(fd == -1) << "Tempfile can't be opened with O_DIRECT: "
<< folly::errnoStr(errno);
SCOPE_EXIT {
::close(fd);
};
folly::EventBase evb(std::move(backend));
auto* backendPtr = dynamic_cast<folly::IoUringBackend*>(evb.getBackend());
CHECK(!!backendPtr);
size_t num = 0;
std::string writeData(kIovSize, 'A'), readData(kIovSize, 'Z');
std::vector<std::string> writeDataVec(kNumIov, writeData),
readDataVec(kNumIov, readData);
std::vector<std::vector<std::string>> writeDataVecVec(
kNumBlocks, writeDataVec),
readDataVecVec(kNumBlocks, readDataVec);
CHECK(readDataVec != writeDataVec);
std::vector<std::vector<struct iovec>> readDataIov, writeDataIov;
std::vector<size_t> lenVec;
readDataIov.reserve(kNumBlocks);
writeDataIov.reserve(kNumBlocks);
lenVec.reserve(kNumBlocks);
for (size_t i = 0; i < kNumBlocks; i++) {
size_t len = 0;
std::vector<struct iovec> readIov, writeIov;
readIov.reserve(kNumIov);
writeIov.reserve(kNumIov);
for (size_t j = 0; j < kNumIov; j++) {
struct iovec riov {
readDataVecVec[i][j].data(), readDataVecVec[i][j].size()
};
readIov.push_back(riov);
struct iovec wiov {
writeDataVecVec[i][j].data(), writeDataVecVec[i][j].size()
};
writeIov.push_back(wiov);
len += riov.iov_len;
}
readDataIov.emplace_back(std::move(readIov));
writeDataIov.emplace_back(std::move(writeIov));
lenVec.emplace_back(len);
}
for (size_t i = 0; i < kNumBlocks; i++) {
folly::IoUringBackend::FileOpCallback writeCb = [&, i](int res) {
CHECK_EQ(res, lenVec[i]);
folly::IoUringBackend::FileOpCallback readCb = [&, i](int res) {
CHECK_EQ(res, lenVec[i]);
CHECK(readDataVecVec[i] == writeDataVecVec[i]);
if (++num == kNumBlocks) {
evb.terminateLoopSoon();
}
};
backendPtr->queueReadv(
fd, readDataIov[i], i * kBlockSize, std::move(readCb));
};
backendPtr->queueWritev(
fd, writeDataIov[i], i * kBlockSize, std::move(writeCb));
}
evb.loopForever();
EXPECT_EQ(num, kNumBlocks);
}
namespace folly { namespace folly {
namespace test { namespace test {
static constexpr size_t kCapacity = 16 * 1024; static constexpr size_t kCapacity = 16 * 1024;
......
...@@ -22,7 +22,9 @@ using folly::IoUring; ...@@ -22,7 +22,9 @@ using folly::IoUring;
namespace folly { namespace folly {
namespace test { namespace test {
namespace async_base_test_lib_detail {
INSTANTIATE_TYPED_TEST_CASE_P(AsyncTest, AsyncTest, IoUring); INSTANTIATE_TYPED_TEST_CASE_P(AsyncTest, AsyncTest, IoUring);
} // namespace async_base_test_lib_detail
} // namespace test } // namespace test
} // namespace folly } // namespace folly
......
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