From 48f58d58cf17586490b9b4801fb6358543e1b0ed Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 15 Mar 2019 20:16:35 +0000 Subject: [PATCH] fuse(4): add tests for the FUSE_ASYNC_READ option Sponsored by: The FreeBSD Foundation --- tests/sys/fs/fuse/read.cc | 161 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/tests/sys/fs/fuse/read.cc b/tests/sys/fs/fuse/read.cc index 61a160aeffbb..42072f8193fe 100644 --- a/tests/sys/fs/fuse/read.cc +++ b/tests/sys/fs/fuse/read.cc @@ -55,6 +55,7 @@ void expect_lookup(const char *relpath, uint64_t ino) }; class AioRead: public Read { +public: virtual void SetUp() { const char *node = "vfs.aio.enable_unsafe"; int val = 0; @@ -69,6 +70,13 @@ virtual void SetUp() { } }; +class AsyncRead: public AioRead { + virtual void SetUp() { + m_init_flags = FUSE_ASYNC_READ; + AioRead::SetUp(); + } +}; + class ReadAhead: public Read, public WithParamInterface { virtual void SetUp() { m_maxreadahead = GetParam(); @@ -108,6 +116,159 @@ TEST_F(AioRead, aio_read) /* Deliberately leak fd. close(2) will be tested in release.cc */ } +/* + * Without the FUSE_ASYNC_READ mount option, fuse(4) should ensure that there + * is at most one outstanding read operation per file handle + */ +TEST_F(AioRead, async_read_disabled) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = 50; + char buf0[bufsize], buf1[bufsize]; + off_t off0 = 0; + off_t off1 = 4096; + struct aiocb iocb0, iocb1; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, bufsize); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ && + in->header.nodeid == ino && + in->body.read.fh == FH && + in->body.read.offset == (uint64_t)off0 && + in->body.read.size == bufsize); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in __unused, auto &out __unused) { + /* Filesystem is slow to respond */ + })); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ && + in->header.nodeid == ino && + in->body.read.fh == FH && + in->body.read.offset == (uint64_t)off1 && + in->body.read.size == bufsize); + }, Eq(true)), + _) + ).Times(0); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + /* + * Submit two AIO read requests, and respond to neither. If the + * filesystem ever gets the second read request, then we failed to + * limit outstanding reads. + */ + iocb0.aio_nbytes = bufsize; + iocb0.aio_fildes = fd; + iocb0.aio_buf = buf0; + iocb0.aio_offset = off0; + iocb0.aio_sigevent.sigev_notify = SIGEV_NONE; + ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno); + + iocb1.aio_nbytes = bufsize; + iocb1.aio_fildes = fd; + iocb1.aio_buf = buf1; + iocb1.aio_offset = off1; + iocb1.aio_sigevent.sigev_notify = SIGEV_NONE; + ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno); + + /* + * Sleep for awhile to make sure the kernel has had a chance to issue + * the second read, even though the first has not yet returned + */ + usleep(250'000); + + /* Deliberately leak iocbs */ + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + +/* + * With the FUSE_ASYNC_READ mount option, fuse(4) may issue multiple + * simultaneous read requests on the same file handle. + */ +/* + * Disabled because we don't yet implement FUSE_ASYNC_READ. No bugzilla + * entry, because that's a feature request, not a bug. + */ +TEST_F(AsyncRead, DISABLED_async_read) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + int fd; + ssize_t bufsize = 50; + char buf0[bufsize], buf1[bufsize]; + off_t off0 = 0; + off_t off1 = 4096; + struct aiocb iocb0, iocb1; + + expect_lookup(RELPATH, ino); + expect_open(ino, 0, 1); + expect_getattr(ino, bufsize); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ && + in->header.nodeid == ino && + in->body.read.fh == FH && + in->body.read.offset == (uint64_t)off0 && + in->body.read.size == bufsize); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in __unused, auto &out __unused) { + /* Filesystem is slow to respond */ + })); + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_READ && + in->header.nodeid == ino && + in->body.read.fh == FH && + in->body.read.offset == (uint64_t)off1 && + in->body.read.size == bufsize); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in __unused, auto &out __unused) { + /* Filesystem is slow to respond */ + })); + + fd = open(FULLPATH, O_RDONLY); + ASSERT_LE(0, fd) << strerror(errno); + + /* + * Submit two AIO read requests, but respond to neither. Ensure that + * we received both. + */ + iocb0.aio_nbytes = bufsize; + iocb0.aio_fildes = fd; + iocb0.aio_buf = buf0; + iocb0.aio_offset = off0; + iocb0.aio_sigevent.sigev_notify = SIGEV_NONE; + ASSERT_EQ(0, aio_read(&iocb0)) << strerror(errno); + + iocb1.aio_nbytes = bufsize; + iocb1.aio_fildes = fd; + iocb1.aio_buf = buf1; + iocb1.aio_offset = off1; + iocb1.aio_sigevent.sigev_notify = SIGEV_NONE; + ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno); + + /* + * Sleep for awhile to make sure the kernel has had a chance to issue + * both reads. + */ + usleep(250'000); + + /* Deliberately leak iocbs */ + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + /* 0-length reads shouldn't cause any confusion */ TEST_F(Read, direct_io_read_nothing) {