Skip to content

fuse: allow non-extending parallel direct writes on the same file #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions fs/fuse/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -1563,14 +1563,47 @@ static ssize_t fuse_direct_read_iter(struct kiocb *iocb, struct iov_iter *to)
return res;
}

static bool fuse_direct_write_extending_i_size(struct kiocb *iocb,
struct iov_iter *iter)
{
struct inode *inode = file_inode(iocb->ki_filp);

return iocb->ki_pos + iov_iter_count(iter) > i_size_read(inode);
}

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 exclusive_lock =
!(ff->open_flags & FOPEN_PARALLEL_DIRECT_WRITES) ||
iocb->ki_flags & IOCB_APPEND ||
fuse_direct_write_extending_i_size(iocb, from);

/*
* Take exclusive lock if
* - Parallel direct writes are disabled - a user space decision
* - Parallel direct writes are enabled and i_size is being extended.
* This might not be needed at all, but needs further investigation.
*/
if (exclusive_lock)
inode_lock(inode);
else {
inode_lock_shared(inode);

/* A race with truncate might have come up as the decision for
* the lock type was done without holding the lock, check again.
*/
if (fuse_direct_write_extending_i_size(iocb, from)) {
inode_unlock_shared(inode);
inode_lock(inode);
exclusive_lock = true;
}
}

/* Don't allow parallel writes to the same file */
inode_lock(inode);
res = generic_write_checks(iocb, from);
if (res > 0) {
if (!is_sync_kiocb(iocb) && iocb->ki_flags & IOCB_DIRECT) {
Expand All @@ -1581,7 +1614,10 @@ static ssize_t fuse_direct_write_iter(struct kiocb *iocb, struct iov_iter *from)
fuse_write_update_attr(inode, iocb->ki_pos, res);
}
}
inode_unlock(inode);
if (exclusive_lock)
inode_unlock(inode);
else
inode_unlock_shared(inode);

return res;
}
Expand Down Expand Up @@ -2938,6 +2974,7 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *iter)

if (iov_iter_rw(iter) == WRITE) {
fuse_write_update_attr(inode, pos, ret);
/* For extending writes we already hold exclusive lock */
if (ret < 0 && offset + count > i_size)
fuse_do_truncate(file);
}
Expand Down
2 changes: 2 additions & 0 deletions include/uapi/linux/fuse.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,13 +302,15 @@ struct fuse_file_lock {
* 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_FILE_CREATED: the file was indeed created
* FOPEN_PARALLEL_DIRECT_WRITES: Allow concurrent direct writes on the same inode
*/
#define FOPEN_DIRECT_IO (1 << 0)
#define FOPEN_KEEP_CACHE (1 << 1)
#define FOPEN_NONSEEKABLE (1 << 2)
#define FOPEN_CACHE_DIR (1 << 3)
#define FOPEN_STREAM (1 << 4)
#define FOPEN_NOFLUSH (1 << 5)
#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6)
#define FOPEN_FILE_CREATED (1 << 7)

/**
Expand Down