[PATCH 1/1] FUSE: Allow parallel direct writes on the same file

From: Dharmendra Singh
Date: Fri Apr 08 2022 - 02:19:06 EST


As of now, in Fuse, direct writes on the same file are serialized
over inode lock i.e we hold inode lock for the whole duration of
the write request. This serialization works pretty well for the FUSE
user space implementations which rely on this inode lock for their
cache/data integrity etc. But it hurts badly such FUSE implementations
which has their own ways of mainting data/cache integrity and does not
use this serialization at all.

This patch allows parallel direct writes on the same file with the
help of a flag called FOPEN_PARALLEL_WRITES. If this flag is set on
the file (flag is passed from libfuse to fuse kernel as part of file
open/create), we do not hold inode lock for the whole duration of the
request, instead acquire it only to protect updates on certain fields
of the inode. FUSE implementations which rely on this inode lock can
continue to do so and this is default behaviour.

Signed-off-by: Dharmendra Singh <dsingh@xxxxxxx>
---
fs/fuse/file.c | 38 ++++++++++++++++++++++++++++++++++----
include/uapi/linux/fuse.h | 2 ++
2 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 37eebfb90500..d3e8f44c1228 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -1465,6 +1465,8 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter,
int err = 0;
struct fuse_io_args *ia;
unsigned int max_pages;
+ bool p_write = write &&
+ (ff->open_flags & FOPEN_PARALLEL_WRITES) ? true : false;

max_pages = iov_iter_npages(iter, fc->max_pages);
ia = fuse_io_alloc(io, max_pages);
@@ -1472,10 +1474,11 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter,
return -ENOMEM;

if (!cuse && fuse_range_is_writeback(inode, idx_from, idx_to)) {
- if (!write)
+ /* Parallel write does not come with inode lock held */
+ if (!write || p_write)
inode_lock(inode);
fuse_sync_writes(inode);
- if (!write)
+ if (!write || p_write)
inode_unlock(inode);
}

@@ -1568,22 +1571,36 @@ static ssize_t fuse_direct_read_iter(struct kiocb *iocb, struct iov_iter *to)
static ssize_t fuse_direct_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
struct inode *inode = file_inode(iocb->ki_filp);
+ struct file *file = iocb->ki_filp;
+ struct fuse_file *ff = file->private_data;
struct fuse_io_priv io = FUSE_IO_PRIV_SYNC(iocb);
ssize_t res;
+ bool p_write = ff->open_flags & FOPEN_PARALLEL_WRITES ? true : false;
+ bool unlock_inode = true;

/* Don't allow parallel writes to the same file */
inode_lock(inode);
res = generic_write_checks(iocb, from);
if (res > 0) {
+ /* Allow parallel writes on the inode by unlocking it */
+ if (p_write) {
+ inode_unlock(inode);
+ unlock_inode = false;
+ }
if (!is_sync_kiocb(iocb) && iocb->ki_flags & IOCB_DIRECT) {
res = fuse_direct_IO(iocb, from);
} else {
res = fuse_direct_io(&io, from, &iocb->ki_pos,
FUSE_DIO_WRITE);
+ if (p_write) {
+ inode_lock(inode);
+ unlock_inode = true;
+ }
fuse_write_update_attr(inode, iocb->ki_pos, res);
}
}
- inode_unlock(inode);
+ if (unlock_inode)
+ inode_unlock(inode);

return res;
}
@@ -2850,10 +2867,16 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
size_t count = iov_iter_count(iter), shortened = 0;
loff_t offset = iocb->ki_pos;
struct fuse_io_priv *io;
-
+ bool p_write = (iov_iter_rw(iter) == WRITE &&
+ ff->open_flags & FOPEN_PARALLEL_WRITES);
pos = offset;
inode = file->f_mapping->host;
+
+ if (p_write)
+ inode_lock(inode);
i_size = i_size_read(inode);
+ if (p_write)
+ inode_unlock(inode);

if ((iov_iter_rw(iter) == READ) && (offset >= i_size))
return 0;
@@ -2924,9 +2947,16 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
kref_put(&io->refcnt, fuse_io_release);

if (iov_iter_rw(iter) == WRITE) {
+
+ if (p_write)
+ inode_lock(inode);
+
fuse_write_update_attr(inode, pos, ret);
if (ret < 0 && offset + count > i_size)
fuse_do_truncate(file);
+
+ if (p_write)
+ inode_unlock(inode);
}

return ret;
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index a28dd60078ff..07f00dfeb0ce 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -301,6 +301,7 @@ struct fuse_file_lock {
* FOPEN_CACHE_DIR: allow caching this directory
* FOPEN_STREAM: the file is stream-like (no file position at all)
* FOPEN_NOFLUSH: don't flush data cache on close (unless FUSE_WRITEBACK_CACHE)
+ * FOPEN_PARALLEL_WRITES: Allow concurrent writes on the same inode
*/
#define FOPEN_DIRECT_IO (1 << 0)
#define FOPEN_KEEP_CACHE (1 << 1)
@@ -308,6 +309,7 @@ struct fuse_file_lock {
#define FOPEN_CACHE_DIR (1 << 3)
#define FOPEN_STREAM (1 << 4)
#define FOPEN_NOFLUSH (1 << 5)
+#define FOPEN_PARALLEL_WRITES (1 << 6)

/**
* INIT request/reply flags
--
2.17.1