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
This commit is contained in:
Alan Somers 2019-05-24 05:12:43 +00:00
parent ad587bc5df
commit e97ae4ad2d
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/projects/fuse2/; revision=348224
4 changed files with 46 additions and 29 deletions

View File

@ -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);

View File

@ -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 ( \

View File

@ -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) {

View File

@ -37,6 +37,7 @@ extern "C" {
#include <aio.h>
#include <fcntl.h>
#include <semaphore.h>
#include <unistd.h>
}
@ -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 */
}