ircd::fs: Add asio glue for io_uring read/write. (closes #37)

This commit is contained in:
Jason Volk 2022-09-04 17:30:59 -07:00
parent da55b185da
commit ae9f2c1199
4 changed files with 223 additions and 38 deletions

View File

@ -53,11 +53,26 @@ namespace boost
#include <boost/system/system_error.hpp>
#include <boost/date_time/posix_time/ptime.hpp>
// In boost 1.78+ io_uring(7) becomes the core event loop replacing epoll(7)
// and used for network operations.
#if defined(HAVE_LIBURING_H) \
&& IRCD_USE_URING == 1 \
&& BOOST_VERSION >= 107800
#define BOOST_ASIO_HAS_IO_URING
#define BOOST_ASIO_DISABLE_EPOLL
#define IRCD_USE_ASIO_IO_URING 1
#else
#define IRCD_USE_ASIO_IO_URING 0
#endif
// In boost 1.79+ asio implements some filesystem operations we can use. While
// these are available in 1.78 they were buggy for our purposes until 1.79.
#if BOOST_VERSION >= 107900
#define IRCD_USE_ASIO_READ 1
#define IRCD_USE_ASIO_WRITE 1
#else
#define IRCD_USE_ASIO_READ 0
#define IRCD_USE_ASIO_WRITE 0
#endif
#include <boost/asio/detail/config.hpp>

View File

@ -31,4 +31,7 @@ namespace ircd::fs
// useful for progressive readv()'s filling the buffers.
const_iovec_view make_iov(const iovec_view &, const const_buffers &, const size_t &off = 0);
const_iovec_view make_iov(const iovec_view &, const mutable_buffers &, const size_t &off = 0);
// For boost::asio; internal
template<class T> vector_view<const T> make_iov(T *, const const_iovec_view &);
}

View File

@ -26,6 +26,7 @@ namespace ircd::fs::support
extern const bool aio;
extern const bool aio_fsync;
extern const bool aio_fdsync;
extern const bool iou;
// Test if O_DIRECT supported at target path
bool direct_io(const string_view &path);

View File

@ -65,16 +65,10 @@ noexcept
void
ircd::fs::init_dump_info()
{
const bool support_async
{
false || aio::system
};
if(!support_async)
if(unlikely(!support::aio && !support::iou))
log::warning
{
log, "Support for asynchronous filesystem IO has not been"
" established. Filesystem IO is degraded to synchronous system calls."
log, "Filesystem IO is degraded to synchronous system calls."
};
}
@ -234,15 +228,20 @@ ircd::fs::support::aio
#endif
};
decltype(ircd::fs::support::iou)
ircd::fs::support::iou
{
#if IRCD_USE_ASIO_IO_URING == 1
info::kernel_version[0] > 5 ||
(info::kernel_version[0] >= 5 && info::kernel_version[1] >= 1)
#else
false
#endif
};
void
ircd::fs::support::dump_info()
{
#if IRCD_USE_AIO
const bool support_async {true};
#else
const bool support_async {false};
#endif
char support[128] {0};
const auto _append{[&support]
(const string_view &name, const bool &avail, const int &enable)
@ -256,7 +255,8 @@ ircd::fs::support::dump_info()
});
}};
_append("async", support_async, -1);
_append("iou", iou, IRCD_USE_ASIO_READ);
_append("aio", aio, -1);
_append("preadv2", preadv2, -1);
_append("pwritev2", pwritev2, -1);
_append("SYNC", sync, -1);
@ -763,9 +763,18 @@ ircd::fs::flush(const fd &fd,
// fs/read.h
//
ircd::fs::read_opts
const ircd::fs::read_opts_default
{};
namespace ircd::fs
{
static int flags(const read_opts &);
static size_t _read_preadv2(const fd &, const const_iovec_view &, const read_opts &);
static size_t _read_preadv(const fd &, const const_iovec_view &, const read_opts &);
static size_t _read_asio(const fd &, const const_iovec_view &, const read_opts &);
static size_t _read(const fd &, const const_iovec_view &, const read_opts &);
static size_t _read_asio(const vector_view<read_op> &);
}
decltype(ircd::fs::read_opts_default)
ircd::fs::read_opts_default;
size_t
ircd::fs::prefetch(const fd &fd,
@ -888,10 +897,13 @@ ircd::fs::read(const vector_view<read_op> &op)
};
}
#ifdef IRCD_USE_AIO
if(likely(aio::system && aio && !all))
return aio::read(op);
#endif
if constexpr(IRCD_USE_ASIO_READ)
if(likely(support::iou && aio && !all))
return _read_asio(op);
if constexpr(IRCD_USE_AIO)
if(likely(aio::system && aio && !all))
return aio::read(op);
// Fallback to sequential read operations
size_t ret(0);
@ -911,13 +923,43 @@ ircd::fs::read(const vector_view<read_op> &op)
return ret;
}
namespace ircd::fs
#if IRCD_USE_ASIO_READ
size_t
ircd::fs::_read_asio(const vector_view<read_op> &op)
{
static int flags(const read_opts &opts);
static size_t _read_preadv2(const fd &, const const_iovec_view &, const read_opts &);
static size_t _read_preadv(const fd &, const const_iovec_view &, const read_opts &);
static size_t _read(const fd &, const const_iovec_view &, const read_opts &);
const auto &ops(op.size());
std::optional<asio::random_access_file> d[ops];
const unwind release{[&d]
{
for(auto &_d : d)
if(likely(_d))
_d->release();
}};
for(uint i(0); i < ops; ++i)
{
assert(op[i].fd);
d[i].emplace(ios::get(), int(*op[i].fd));
}
size_t ret {0};
ctx::latch latch {ops};
for(uint i(0); i < ops; ++i)
d[i]->async_read_some_at(op[i].opts->offset, op[i].bufs, [i, &op, &ret, &latch]
(const auto &ec, const size_t &bytes)
{
if(ec && ec != net::eof)
op[i].eptr = make_system_eptr(ec);
op[i].ret = bytes;
ret += bytes;
latch.count_down();
});
latch.wait();
return ret;
}
#endif
/// Read from file descriptor fd into buffers. The number of bytes read into
/// the buffers is returned. By default (via read_opts.all) this call will
@ -988,6 +1030,10 @@ ircd::fs::_read(const fd &fd,
{
assert(opts.op == op::READ);
if constexpr(IRCD_USE_ASIO_READ)
if(likely(support::iou && opts.aio))
return _read_asio(fd, iov, opts);
if constexpr(IRCD_USE_AIO)
if(likely(aio::system && opts.aio))
return aio::read(fd, iov, opts);
@ -1001,6 +1047,56 @@ ircd::fs::_read(const fd &fd,
#endif
}
#if IRCD_USE_ASIO_READ
size_t
ircd::fs::_read_asio(const fd &fd,
const const_iovec_view &iov,
const read_opts &opts)
{
assert(bytes(iov) > 0);
assert(opts.offset >= 0);
asio::mutable_buffer buf[iov.size()];
const auto bufs
{
make_iov(buf, iov)
};
asio::random_access_file d
{
ios::get(), int(fd)
};
const unwind release{[&d]
{
d.release();
}};
const auto interruption{[&d, &opts]
(ctx::ctx *const &interruptor)
{
if(opts.interruptible)
d.cancel();
}};
boost::system::error_code ec;
size_t ret {0}; continuation
{
continuation::asio_predicate, interruption, [&ret, &d, &opts, &bufs, &ec]
(auto &yield)
{
ret = d.async_read_some_at(opts.offset, bufs, yield[ec]);
}
};
assert(ret || ec == net::eof);
if(unlikely(ec && ec != net::eof))
throw_system_error(ec);
return ret;
}
#endif
size_t
ircd::fs::_read_preadv(const fd &fd,
const const_iovec_view &iov,
@ -1073,9 +1169,8 @@ ircd::fs::flags(const read_opts &opts)
// fs/write.h
//
ircd::fs::write_opts
const ircd::fs::write_opts_default
{};
decltype(ircd::fs::write_opts_default)
ircd::fs::write_opts_default;
void
ircd::fs::allocate(const fd &fd,
@ -1254,6 +1349,15 @@ ircd::fs::append(const fd &fd,
// write
//
namespace ircd::fs
{
static int flags(const write_opts &opts);
static size_t _write_pwritev2(const fd &, const const_iovec_view &, const write_opts &);
static size_t _write_pwritev(const fd &, const const_iovec_view &, const write_opts &);
static size_t _write_asio(const fd &, const const_iovec_view &, const write_opts &);
static size_t _write(const fd &, const const_iovec_view &, const write_opts &);
}
ircd::const_buffer
ircd::fs::write(const string_view &path,
const const_buffer &buf,
@ -1302,14 +1406,6 @@ ircd::fs::write(const string_view &path,
return write(fd, bufs, opts);
}
namespace ircd::fs
{
static int flags(const write_opts &opts);
static size_t _write_pwritev2(const fd &, const const_iovec_view &, const write_opts &);
static size_t _write_pwritev(const fd &, const const_iovec_view &, const write_opts &);
static size_t _write(const fd &, const const_iovec_view &, const write_opts &);
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstack-usage="
size_t
@ -1363,6 +1459,10 @@ ircd::fs::_write(const fd &fd,
{
assert(opts.op == op::WRITE);
if constexpr(IRCD_USE_ASIO_WRITE)
if(likely(support::iou && opts.aio))
return _write_asio(fd, iov, opts);
if constexpr(IRCD_USE_AIO)
if(likely(aio::system && opts.aio))
return aio::write(fd, iov, opts);
@ -1376,6 +1476,55 @@ ircd::fs::_write(const fd &fd,
#endif
}
#if IRCD_USE_ASIO_WRITE
size_t
ircd::fs::_write_asio(const fd &fd,
const const_iovec_view &iov,
const write_opts &opts)
{
assert(bytes(iov) > 0);
assert(opts.offset >= 0 || opts.offset == -1);
asio::const_buffer buf[iov.size()];
const auto bufs
{
make_iov(buf, iov)
};
asio::random_access_file d
{
ios::get(), int(fd)
};
const unwind release{[&d]
{
d.release();
}};
const auto interruption{[&d, &opts]
(ctx::ctx *const &interruptor)
{
if(opts.interruptible)
d.cancel();
}};
boost::system::error_code ec;
size_t ret {0}; continuation
{
continuation::asio_predicate, interruption, [&ret, &d, &opts, &bufs, &ec]
(auto &yield)
{
ret = d.async_write_some_at(opts.offset, bufs, yield[ec]);
}
};
if(unlikely(ec))
throw_system_error(ec);
return ret;
}
#endif
size_t
ircd::fs::_write_pwritev(const fd &fd,
const const_iovec_view &iov,
@ -2608,6 +2757,23 @@ ircd::fs::aio::translate(const int &val)
// fs/iov.h
//
template<class T>
ircd::vector_view<const T>
ircd::fs::make_iov(T *const buf,
const const_iovec_view &iov)
{
for(size_t i(0); i < iov.size(); ++i)
buf[i] = T
{
iov[i].iov_base, iov[i].iov_len
};
return vector_view<const T>
{
buf, iov.size()
};
}
ircd::fs::const_iovec_view
ircd::fs::make_iov(const iovec_view &iov,
const mutable_buffers &bufs,