From 6aa6452d3ef9977108d18e8462d2b0319702e4cb Mon Sep 17 00:00:00 2001 From: Antonio SJ Musumeci Date: Mon, 23 Oct 2023 22:32:42 -0500 Subject: [PATCH] Add flushonclose feature --- README.md | 26 ++++++++++++++++- libfuse/include/fuse_common.h | 2 ++ libfuse/lib/fuse_lowlevel.c | 2 ++ man/mergerfs.1 | 25 ++++++++++++++++ src/config.cpp | 4 ++- src/config.hpp | 4 ++- src/config_flushonclose.cpp | 54 +++++++++++++++++++++++++++++++++++ src/config_flushonclose.hpp | 31 ++++++++++++++++++++ src/fuse_create.cpp | 28 ++++++++++++++++++ src/fuse_open.cpp | 21 ++++++++++++++ src/fuse_opendir.cpp | 2 ++ 11 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 src/config_flushonclose.cpp create mode 100644 src/config_flushonclose.hpp diff --git a/README.md b/README.md index 1032ec16..f5ac9842 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,10 @@ These options are the same regardless of whether you use them with the to the same as the process thread count. (default: 0) * **pin-threads=STR**: Selects a strategy to pin threads to CPUs (default: unset) -* **scheduling-priority=INT**: Set mergerfs' scheduling +* **flush-on-close=never|always|opened-for-write**: Flush data cache + on file close. Mostly for when writeback is enabled or merging + network filesystems. (default: opened-for-write) + * **scheduling-priority=INT**: Set mergerfs' scheduling priority. Valid values range from -20 to 19. See `setpriority` man page for more details. (default: -10) * **fsname=STR**: Sets the name of the filesystem as seen in @@ -926,6 +929,27 @@ The options `statfs` and `statfs_ignore` can be used to modify `statfs` behavior. +#### flush-on-close + +https://lkml.kernel.org/linux-fsdevel/20211024132607.1636952-1-amir73il@gmail.com/T/ + +By default FUSE would issue a flush before the release of a file +descriptor. This was considered a bit aggressive and a feature added +to give the FUSE server the ability to choose when that happens. + +Options: +* always +* never +* opened-for-write + +For now it defaults to "opened-for-write" which is less aggressive +than the behavior before this feature was added. It should not be a +problem because the flush is really only relevant when a file is +written to. Given flush is irrelevant for many filesystems in the +future a branch specific flag may be added so only files opened on a +specific branch would be flushed on close. + + # ERROR HANDLING POSIX filesystem functions offer a single return code meaning that diff --git a/libfuse/include/fuse_common.h b/libfuse/include/fuse_common.h index 4f9a28ff..a5cf72b1 100644 --- a/libfuse/include/fuse_common.h +++ b/libfuse/include/fuse_common.h @@ -86,6 +86,8 @@ struct fuse_file_info_t uint32_t parallel_direct_writes:1; + uint32_t noflush:1; + /** File handle. May be filled in by filesystem in open(). Available in all other file operations */ uint64_t fh; diff --git a/libfuse/lib/fuse_lowlevel.c b/libfuse/lib/fuse_lowlevel.c index 6e4e4788..37f24dd3 100644 --- a/libfuse/lib/fuse_lowlevel.c +++ b/libfuse/lib/fuse_lowlevel.c @@ -275,6 +275,8 @@ fill_open(struct fuse_open_out *arg_, arg_->open_flags |= FOPEN_CACHE_DIR; if(ffi_->parallel_direct_writes) arg_->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES; + if(ffi_->noflush) + arg_->open_flags |= FOPEN_NOFLUSH; } int diff --git a/man/mergerfs.1 b/man/mergerfs.1 index 296ce519..95e37fc5 100644 --- a/man/mergerfs.1 +++ b/man/mergerfs.1 @@ -321,11 +321,18 @@ by the number of process threads plus read thread count. \f[B]pin-threads=STR\f[R]: Selects a strategy to pin threads to CPUs (default: unset) .IP \[bu] 2 +\f[B]flush-on-close=never|always|opened-for-write\f[R]: Flush data cache +on file close. +Mostly for when writeback is enabled or merging network filesystems. +(default: opened-for-write) +.RS 2 +.IP \[bu] 2 \f[B]scheduling-priority=INT\f[R]: Set mergerfs\[cq] scheduling priority. Valid values range from -20 to 19. See \f[C]setpriority\f[R] man page for more details. (default: -10) +.RE .IP \[bu] 2 \f[B]fsname=STR\f[R]: Sets the name of the filesystem as seen in \f[B]mount\f[R], \f[B]df\f[R], etc. @@ -1321,6 +1328,24 @@ be included when checking the mount\[cq]s stats. .PP The options \f[C]statfs\f[R] and \f[C]statfs_ignore\f[R] can be used to modify \f[C]statfs\f[R] behavior. +.SS flush-on-close +.PP +https://lkml.kernel.org/linux-fsdevel/20211024132607.1636952-1-amir73il\[at]gmail.com/T/ +.PP +By default FUSE would issue a flush before the release of a file +descriptor. +This was considered a bit aggressive and a feature added to give the +FUSE server the ability to choose when that happens. +.PP +Options: * always * never * opened-for-write +.PP +For now it defaults to \[lq]opened-for-write\[rq] which is less +aggressive than the behavior before this feature was added. +It should not be a problem because the flush is really only relevant +when a file is written to. +Given flush is irrelevant for many filesystems in the future a branch +specific flag may be added so only files opened on a specific branch +would be flushed on close. .SH ERROR HANDLING .PP POSIX filesystem functions offer a single return code meaning that there diff --git a/src/config.cpp b/src/config.cpp index 2cb64238..3ea81954 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -94,8 +94,9 @@ Config::Config() category(func), direct_io(false), dropcacheonclose(false), - fsname(), + flushonclose(FlushOnClose::ENUM::ALWAYS), follow_symlinks(FollowSymlinks::ENUM::NEVER), + fsname(), func(), fuse_msg_size(FUSE_MAX_MAX_PAGES), ignorepponrename(false), @@ -149,6 +150,7 @@ Config::Config() _map["category.search"] = &category.search; _map["direct_io"] = &direct_io; _map["dropcacheonclose"] = &dropcacheonclose; + _map["flush-on-close"] = &flushonclose; _map["follow-symlinks"] = &follow_symlinks; _map["fsname"] = &fsname; _map["func.access"] = &func.access; diff --git a/src/config.hpp b/src/config.hpp index 6f0300eb..a9804358 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -19,6 +19,7 @@ #include "branches.hpp" #include "category.hpp" #include "config_cachefiles.hpp" +#include "config_flushonclose.hpp" #include "config_follow_symlinks.hpp" #include "config_inodecalc.hpp" #include "config_link_exdev.hpp" @@ -116,8 +117,9 @@ public: Categories category; ConfigBOOL direct_io; ConfigBOOL dropcacheonclose; - ConfigSTR fsname; + FlushOnClose flushonclose; FollowSymlinks follow_symlinks; + ConfigSTR fsname; Funcs func; ConfigUINT64 fuse_msg_size; ConfigBOOL ignorepponrename; diff --git a/src/config_flushonclose.cpp b/src/config_flushonclose.cpp new file mode 100644 index 00000000..10339262 --- /dev/null +++ b/src/config_flushonclose.cpp @@ -0,0 +1,54 @@ +/* + ISC License + + Copyright (c) 2023, Antonio SJ Musumeci + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "config_flushonclose.hpp" +#include "ef.hpp" +#include "errno.hpp" + +template<> +std::string +FlushOnClose::to_string() const +{ + switch(_data) + { + case FlushOnClose::ENUM::NEVER: + return "never"; + case FlushOnClose::ENUM::OPENED_FOR_WRITE: + return "opened-for-write"; + case FlushOnClose::ENUM::ALWAYS: + return "always"; + } + + return {}; +} + +template<> +int +FlushOnClose::from_string(const std::string &s_) +{ + if(s_ == "never") + _data = FlushOnClose::ENUM::NEVER; + ef(s_ == "opened-for-write") + _data = FlushOnClose::ENUM::OPENED_FOR_WRITE; + ef(s_ == "always") + _data = FlushOnClose::ENUM::ALWAYS; + else + return -EINVAL; + + return 0; +} diff --git a/src/config_flushonclose.hpp b/src/config_flushonclose.hpp new file mode 100644 index 00000000..4ad74ab1 --- /dev/null +++ b/src/config_flushonclose.hpp @@ -0,0 +1,31 @@ +/* + ISC License + + Copyright (c) 2023, Antonio SJ Musumeci + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#pragma once + +#include "enum.hpp" + + +enum class FlushOnCloseEnum + { + NEVER, + OPENED_FOR_WRITE, + ALWAYS + }; + +typedef Enum FlushOnClose; diff --git a/src/fuse_create.cpp b/src/fuse_create.cpp index a6b15e18..45ee5c68 100644 --- a/src/fuse_create.cpp +++ b/src/fuse_create.cpp @@ -51,6 +51,31 @@ namespace l *flags_ &= ~O_APPEND; } + static + bool + rdonly(const int flags_) + { + return ((flags_ & O_ACCMODE) == O_RDONLY); + } + + static + bool + calculate_flush(FlushOnClose const flushonclose_, + int const flags_) + { + switch(flushonclose_) + { + case FlushOnCloseEnum::NEVER: + return false; + case FlushOnCloseEnum::OPENED_FOR_WRITE: + return !l::rdonly(flags_); + case FlushOnCloseEnum::ALWAYS: + return true; + } + + return true; + } + static void config_to_ffi_flags(Config::Read &cfg_, @@ -200,6 +225,9 @@ namespace FUSE if(cfg->writeback_cache) l::tweak_flags_writeback_cache(&ffi_->flags); + ffi_->noflush = !l::calculate_flush(cfg->flushonclose, + ffi_->flags); + rv = l::create(cfg->func.getattr.policy, cfg->func.create.policy, cfg->branches, diff --git a/src/fuse_open.cpp b/src/fuse_open.cpp index 1cb083dc..d49ed797 100644 --- a/src/fuse_open.cpp +++ b/src/fuse_open.cpp @@ -114,6 +114,24 @@ namespace l *flags_ &= ~O_APPEND; } + static + bool + calculate_flush(FlushOnClose const flushonclose_, + int const flags_) + { + switch(flushonclose_) + { + case FlushOnCloseEnum::NEVER: + return false; + case FlushOnCloseEnum::OPENED_FOR_WRITE: + return !l::rdonly(flags_); + case FlushOnCloseEnum::ALWAYS: + return true; + } + + return true; + } + static void config_to_ffi_flags(Config::Read &cfg_, @@ -236,6 +254,9 @@ namespace FUSE if(cfg->writeback_cache) l::tweak_flags_writeback_cache(&ffi_->flags); + ffi_->noflush = !l::calculate_flush(cfg->flushonclose, + ffi_->flags); + rv = l::open(cfg->func.open.policy, cfg->branches, fusepath_, diff --git a/src/fuse_opendir.cpp b/src/fuse_opendir.cpp index 43c706ae..f46f3c41 100644 --- a/src/fuse_opendir.cpp +++ b/src/fuse_opendir.cpp @@ -30,6 +30,8 @@ namespace FUSE ffi_->fh = reinterpret_cast(new DirInfo(fusepath_)); + ffi_->noflush = true; + if(cfg->cache_readdir) { ffi_->keep_cache = 1;