From e97ae4ad2df0b8a73ea001bfc1f994acf392832b Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 24 May 2019 05:12:43 +0000 Subject: [PATCH] fusefs: implement FUSE_ASYNC_READ If a daemon sets the FUSE_ASYNC_READ flag during initialization, then the client is allowed to issue multiple concurrent reads for the same file handle. Otherwise concurrent reads are not allowed. This commit implements it. Previously we unconditionally disallowed concurrent reads. Sponsored by: The FreeBSD Foundation --- sys/fs/fuse/fuse_internal.c | 4 ++- sys/fs/fuse/fuse_ipc.h | 1 + sys/fs/fuse/fuse_node.c | 6 ++++ tests/sys/fs/fusefs/read.cc | 64 +++++++++++++++++++++---------------- 4 files changed, 46 insertions(+), 29 deletions(-) diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c index e56de69d6ea6..721a169dd3a3 100644 --- a/sys/fs/fuse/fuse_internal.c +++ b/sys/fs/fuse/fuse_internal.c @@ -764,6 +764,8 @@ fuse_internal_init_callback(struct fuse_ticket *tick, struct uio *uio) if (fuse_libabi_geq(data, 7, 5)) { if (fticket_resp(tick)->len == sizeof(struct fuse_init_out)) { data->max_write = fiio->max_write; + if (fiio->flags & FUSE_ASYNC_READ) + data->dataflags |= FSESS_ASYNC_READ; if (fiio->flags & FUSE_POSIX_LOCKS) data->dataflags |= FSESS_POSIX_LOCKS; if (fiio->flags & FUSE_EXPORT_SUPPORT) @@ -805,7 +807,7 @@ fuse_internal_send_init(struct fuse_data *data, struct thread *td) * the size of a buffer cache block. */ fiii->max_readahead = maxbcachebuf; - fiii->flags = FUSE_EXPORT_SUPPORT | FUSE_POSIX_LOCKS; + fiii->flags = FUSE_ASYNC_READ | FUSE_POSIX_LOCKS | FUSE_EXPORT_SUPPORT ; fuse_insert_callback(fdi.tick, fuse_internal_init_callback); fuse_insert_message(fdi.tick, false); diff --git a/sys/fs/fuse/fuse_ipc.h b/sys/fs/fuse/fuse_ipc.h index b040b671f59f..89a17d7f7e23 100644 --- a/sys/fs/fuse/fuse_ipc.h +++ b/sys/fs/fuse/fuse_ipc.h @@ -221,6 +221,7 @@ struct fuse_data { #define FSESS_NO_DATACACHE 0x0200 /* disable buffer cache */ #define FSESS_NO_NAMECACHE 0x0400 /* disable name cache */ #define FSESS_NO_MMAP 0x0800 /* disable mmap */ +#define FSESS_ASYNC_READ 0x1000 /* allow multiple reads of some file */ #define FSESS_POSIX_LOCKS 0x2000 /* daemon supports POSIX locks */ #define FSESS_EXPORT_SUPPORT 0x10000 /* daemon supports NFS-style lookups */ #define FSESS_MNTOPTS_MASK ( \ diff --git a/sys/fs/fuse/fuse_node.c b/sys/fs/fuse/fuse_node.c index 0f83098264c2..20cf27b16326 100644 --- a/sys/fs/fuse/fuse_node.c +++ b/sys/fs/fuse/fuse_node.c @@ -179,10 +179,12 @@ fuse_vnode_alloc(struct mount *mp, enum vtype vtyp, struct vnode **vpp) { + struct fuse_data *data; struct fuse_vnode_data *fvdat; struct vnode *vp2; int err = 0; + data = fuse_get_mpdata(mp); if (vtyp == VNON) { return EINVAL; } @@ -235,6 +237,10 @@ fuse_vnode_alloc(struct mount *mp, *vpp = NULL; return (err); } + /* Disallow async reads for fifos because UFS does. I don't know why */ + if (data->dataflags & FSESS_ASYNC_READ && vtyp != VFIFO) + VN_LOCK_ASHARE(*vpp); + err = vfs_hash_insert(*vpp, fuse_vnode_hash(nodeid), LK_EXCLUSIVE, td, &vp2, fuse_vnode_cmp, &nodeid); if (err) { diff --git a/tests/sys/fs/fusefs/read.cc b/tests/sys/fs/fusefs/read.cc index 7200f50ab2f7..8cf40144fb47 100644 --- a/tests/sys/fs/fusefs/read.cc +++ b/tests/sys/fs/fusefs/read.cc @@ -37,6 +37,7 @@ extern "C" { #include #include +#include #include } @@ -159,21 +160,22 @@ TEST_F(AioRead, async_read_disabled) ssize_t bufsize = 50; char buf0[bufsize], buf1[bufsize]; off_t off0 = 0; - off_t off1 = 4096; + off_t off1 = 65536; struct aiocb iocb0, iocb1; + volatile sig_atomic_t read_count = 0; - expect_lookup(RELPATH, ino, bufsize); + expect_lookup(RELPATH, ino, 131072); expect_open(ino, 0, 1); 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); + in->body.read.offset == (uint64_t)off0); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in __unused, auto &out __unused) { + ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) { + read_count++; /* Filesystem is slow to respond */ })); EXPECT_CALL(*m_mock, process( @@ -181,11 +183,13 @@ TEST_F(AioRead, async_read_disabled) 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); + in->body.read.offset == (uint64_t)off1); }, Eq(true)), _) - ).Times(0); + ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) { + read_count++; + /* Filesystem is slow to respond */ + })); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); @@ -214,8 +218,12 @@ TEST_F(AioRead, async_read_disabled) * the second read, even though the first has not yet returned */ nap(); + EXPECT_EQ(read_count, 1); - /* Deliberately leak iocbs */ + m_mock->kill_daemon(); + /* Wait for AIO activity to complete, but ignore errors */ + (void)aio_waitcomplete(NULL, NULL); + /* Deliberately leak fd. close(2) will be tested in release.cc */ } @@ -223,11 +231,7 @@ TEST_F(AioRead, async_read_disabled) * 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) +TEST_F(AsyncRead, async_read) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; @@ -236,21 +240,24 @@ TEST_F(AsyncRead, DISABLED_async_read) ssize_t bufsize = 50; char buf0[bufsize], buf1[bufsize]; off_t off0 = 0; - off_t off1 = 4096; + off_t off1 = 65536; struct aiocb iocb0, iocb1; + sem_t sem; - expect_lookup(RELPATH, ino, bufsize); + ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); + + expect_lookup(RELPATH, ino, 131072); expect_open(ino, 0, 1); 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); + in->body.read.offset == (uint64_t)off0); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in __unused, auto &out __unused) { + ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) { + sem_post(&sem); /* Filesystem is slow to respond */ })); EXPECT_CALL(*m_mock, process( @@ -258,11 +265,11 @@ TEST_F(AsyncRead, DISABLED_async_read) 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); + in->body.read.offset == (uint64_t)off1); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in __unused, auto &out __unused) { + ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) { + sem_post(&sem); /* Filesystem is slow to respond */ })); @@ -287,13 +294,14 @@ TEST_F(AsyncRead, DISABLED_async_read) 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. - */ - nap(); + /* Wait until both reads have reached the daemon */ + ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno); + ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno); + + m_mock->kill_daemon(); + /* Wait for AIO activity to complete, but ignore errors */ + (void)aio_waitcomplete(NULL, NULL); - /* Deliberately leak iocbs */ /* Deliberately leak fd. close(2) will be tested in release.cc */ }