From 74812acedd1395e5b03b1940c3ced74371fdd3eb Mon Sep 17 00:00:00 2001 From: Horst Birthelmer Date: Tue, 7 Jul 2026 10:20:10 +0200 Subject: [PATCH 1/2] fuse: take inode lock shared for DLM-serialised buffered writes Add fuse_cache_wr_exclusive_lock() to take the lock shared for normal writes; direct, appending and non-writeback-cache (suid-drop) writes stay exclusive. Extending writes grow i_size under fi->lock before the write and reconcile short writes afterwards. Signed-off-by: Horst Birthelmer --- fs/fuse/file.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 4 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 467093acadbb89..2e6efdbb67fc3e 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1666,6 +1666,35 @@ static ssize_t fuse_writeback_write_iter(struct kiocb *iocb, return written < 0 ? written : total_written; } +/* + * @return true if an exclusive lock is needed for a cached (buffered) write. + */ +static bool fuse_cache_wr_exclusive_lock(struct kiocb *iocb, bool cache_mode) +{ + struct inode *inode = file_inode(iocb->ki_filp); + struct fuse_conn *fc = get_fuse_conn(inode); + + /* + * Without the DLM the inode rwsem is the only cross-writer exclusion. + * Outside writeback-cache mode (cache_mode == false: no writeback cache, + * or a suid/sgid drop that needs notify_change) the write is covered by + * neither the DLM write lock nor a synchronous server round-trip, so + * keep the exclusive lock. + */ + if (!fc->dlm || !cache_mode) + return true; + + /* O_DIRECT writes fall back to generic_file_direct_write(). */ + if (iocb->ki_flags & IOCB_DIRECT) + return true; + + /* Append needs the eventual EOF - always needs an exclusive lock. */ + if (iocb->ki_flags & IOCB_APPEND) + return true; + + return false; +} + static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) { struct file *file = iocb->ki_filp; @@ -1675,7 +1704,10 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) struct inode *inode = mapping->host; ssize_t err, count; struct fuse_conn *fc = get_fuse_conn(inode); + struct fuse_inode *fi = get_fuse_inode(inode); bool writeback = false; + bool cache_mode = false; + bool exclusive; if (fc->writeback_cache) { /* Update size (EOF optimization) and mode (SUID clearing) */ @@ -1684,8 +1716,10 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) if (err) return err; - if (!fc->handle_killpriv_v2 || - !setattr_should_drop_suidgid(idmap, file_inode(file))) { + cache_mode = !fc->handle_killpriv_v2 || + !setattr_should_drop_suidgid(idmap, file_inode(file)); + + if (cache_mode) { writeback = true; /* @@ -1713,7 +1747,11 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) } } - inode_lock(inode); + exclusive = fuse_cache_wr_exclusive_lock(iocb, cache_mode); + if (exclusive) + inode_lock(inode); + else + inode_lock_shared(inode); err = count = generic_write_checks(iocb, from); if (err <= 0) @@ -1732,7 +1770,51 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) written = direct_write_fallback(iocb, from, written, fuse_perform_write(iocb, from)); } else if (writeback) { + loff_t pos = iocb->ki_pos; + loff_t end = pos + count; + loff_t orig_size = 0; + bool extended = false; + + if (fc->dlm && !exclusive && end > i_size_read(inode)) { + /* + * Lockless pre-check above keeps in-bounds writes off + * fi->lock; under the shared inode lock i_size only grows + * (extenders take fi->lock, truncate is excluded), so a + * stale read can only over-trigger this slow path, never + * miss an extension. Re-check authoritatively here. + */ + spin_lock(&fi->lock); + orig_size = i_size_read(inode); + if (end > orig_size) { + i_size_write(inode, end); + extended = true; + } + spin_unlock(&fi->lock); + + /* Zero the tail of the folio straddling the old EOF. */ + if (extended && orig_size < pos) + pagecache_isize_extended(inode, orig_size, pos); + } + written = fuse_writeback_write_iter(iocb, from, file); + + /* + * Reconcile the speculative extension with what was actually + * written. Only retract the tail if no concurrent extender has + * pushed i_size past our claim; otherwise [reached, end) is a + * legitimate hole inside their extension and must remain. + */ + if (extended) { + loff_t reached = written > 0 ? pos + written : orig_size; + + if (reached < end) { + spin_lock(&fi->lock); + if (i_size_read(inode) == end) + i_size_write(inode, reached); + spin_unlock(&fi->lock); + } + } + if (written < 0) { err = written; goto out; @@ -1741,7 +1823,10 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) written = fuse_perform_write(iocb, from); } out: - inode_unlock(inode); + if (exclusive) + inode_unlock(inode); + else + inode_unlock_shared(inode); if (written > 0) written = generic_write_sync(iocb, written); From f6aadcc904bb18345204a50dd96dd78205d17665 Mon Sep 17 00:00:00 2001 From: Horst Birthelmer Date: Tue, 7 Jul 2026 12:02:37 +0200 Subject: [PATCH 2/2] fuse: switch to direct IO on an inode-invalidation notify storm A FUSE_NOTIFY_INVAL_INODE data invalidation means another (remote) entity is modifying the file. Rather than react to a single notify, keep a per-inode moving average of how fast data invalidations arrive for the whole file: an EWMA of the inter-arrival interval, updated under fi->lock on every notify (fuse_notify_inval_hot()). The inode is latched only once the average spacing drops below an internal threshold (FUSE_NOTIFY_DIO_INTERVAL) while a local writer is open; a lone or occasional notify keeps the average high and does not trip the switch. The heuristic has no external knob -- its parameters (EWMA weight, threshold, seed) are source-level constants. Introduce the forced-direct-IO latch (FUSE_I_FORCE_DIO): - fuse_reverse_inval_inode() folds each data invalidation into the moving average and sets the latch when it trips with a local writer present; - fuse_file_{read,write}_iter() and fuse_cache_write_iter() route to the direct path while latched; fuse_dio_{wr_exclusive_lock,lock,unlock}() use the shared parallel-dio path and bypass the cached/uncached accounting; - fuse_file_io_open() opens new files uncached so they do not re-enter caching mode; - fuse_prepare_release() clears the latch once the last writer is gone and fuse_file_release() drops any clean folios a racing read repopulated; fuse_file_mmap() reverts to caching mode (a mapping needs the page cache). Latching to direct IO is only coherent if no buffered write can deposit dirty folios into the page cache after it has been dropped. Add a per-inode rw_semaphore, wb_inval_rwsem, to serialise the buffered-write page-cache dirtying against the latch transition. The writeback path holds it for read around the dirtying and re-checks the latch under it; fuse_reverse_inval_inode() holds it for write around its invalidate + latch set. The notification may be delivered by the same server thread that still owes a reply to an in-flight write holding the inode lock, so it takes the rwsem with a trylock and never blocks: if the writer has gone it skips the latch and only invalidates the notified range. The writer's read-side section stays free of server round-trips because under fc->dlm the partial-write RMW read is skipped. Signed-off-by: Horst Birthelmer --- fs/fuse/file.c | 124 ++++++++++++++++++++++++++++++++++++++++++++--- fs/fuse/fuse_i.h | 56 +++++++++++++++++++++ fs/fuse/inode.c | 95 +++++++++++++++++++++++++++++++++++- fs/fuse/iomode.c | 10 ++++ 4 files changed, 275 insertions(+), 10 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 2e6efdbb67fc3e..737c1fb670df28 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -411,6 +411,18 @@ static void fuse_prepare_release(struct fuse_inode *fi, struct fuse_file *ff, if (likely(fi)) { spin_lock(&fi->lock); list_del(&ff->write_entry); + /* + * Leave forced direct IO mode once the last writer is gone: with + * no local writer left there is no cached-write contention with + * the remote modifier that triggered the switch. Restore + * FUSE_I_CACHE_IO_MODE for any frozen cached opens. + */ + if (test_bit(FUSE_I_FORCE_DIO, &fi->state) && + list_empty(&fi->write_files)) { + clear_bit(FUSE_I_FORCE_DIO, &fi->state); + if (fi->iocachectr > 0) + set_bit(FUSE_I_CACHE_IO_MODE, &fi->state); + } spin_unlock(&fi->lock); } spin_lock(&fc->lock); @@ -449,9 +461,24 @@ void fuse_file_release(struct inode *inode, struct fuse_file *ff, struct fuse_inode *fi = get_fuse_inode(inode); struct fuse_release_args *ra = &ff->args->release_args; int opcode = isdir ? FUSE_RELEASEDIR : FUSE_RELEASE; + bool was_force_dio = test_bit(FUSE_I_FORCE_DIO, &fi->state); fuse_prepare_release(fi, ff, open_flags, opcode, false); + /* + * If this release dropped the last writer, fuse_prepare_release() + * cleared the forced-direct-IO latch (under fi->lock). Drop any clean + * folios a read racing the latch may have repopulated so they cannot be + * served stale once caching mode resumes. No inode lock or + * wb_inval_rwsem: release may run on the fuse server thread (async fput + * from aio completion), where blocking on a contended inode lock could + * stall the connection. Writes were routed direct while latched, so + * only clean folios exist and this invalidate is server-free; the last + * writer is gone, so no forced-dio writer can race the drop. + */ + if (was_force_dio && !test_bit(FUSE_I_FORCE_DIO, &fi->state)) + invalidate_inode_pages2(inode->i_mapping); + if (ra && ff->flock) { ra->inarg.release_flags |= FUSE_RELEASE_FLOCK_UNLOCK; ra->inarg.lock_owner = fuse_lock_owner_id(ff->fm->fc, id); @@ -1513,9 +1540,15 @@ static bool fuse_dio_wr_exclusive_lock(struct kiocb *iocb, struct iov_iter *from struct fuse_file *ff = file->private_data; struct inode *inode = file_inode(iocb->ki_filp); struct fuse_inode *fi = get_fuse_inode(inode); + bool force_dio = test_bit(FUSE_I_FORCE_DIO, &fi->state); - /* Server side has to advise that it supports parallel dio writes. */ - if (!(ff->open_flags & FOPEN_PARALLEL_DIRECT_WRITES)) + /* + * Server side has to advise that it supports parallel dio writes. + * When the inode is latched into forced direct IO, parallel writes are + * used unconditionally: the page cache has been flushed and is bypassed + * for this inode. + */ + if (!force_dio && !(ff->open_flags & FOPEN_PARALLEL_DIRECT_WRITES)) return true; /* @@ -1526,7 +1559,7 @@ static bool fuse_dio_wr_exclusive_lock(struct kiocb *iocb, struct iov_iter *from return true; /* shared locks are not allowed with parallel page cache IO */ - if (test_bit(FUSE_I_CACHE_IO_MODE, &fi->state)) + if (!force_dio && test_bit(FUSE_I_CACHE_IO_MODE, &fi->state)) return true; /* Parallel dio beyond EOF is not supported, at least for now. */ @@ -1553,9 +1586,14 @@ static void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from, * should be performed only after taking shared inode lock. * Previous past eof check was without inode lock and might * have raced, so check it again. + * + * Under the forced-dio latch the cached/uncached accounting is + * bypassed (the latch guarantees the cache is flushed and not + * repopulated), so only re-check the past-eof condition. */ if (fuse_io_past_eof(iocb, from) || - fuse_inode_uncached_io_start(fi, NULL) != 0) { + (!test_bit(FUSE_I_FORCE_DIO, &fi->state) && + fuse_inode_uncached_io_start(fi, NULL) != 0)) { inode_unlock_shared(inode); inode_lock(inode); *exclusive = true; @@ -1571,8 +1609,13 @@ static void fuse_dio_unlock(struct kiocb *iocb, bool exclusive) if (exclusive) { inode_unlock(inode); } else { - /* Allow opens in caching mode after last parallel dio end */ - fuse_inode_uncached_io_end(fi); + /* + * Allow opens in caching mode after last parallel dio end. + * Skipped under the forced-dio latch, which never took an + * uncached_io reference in fuse_dio_lock(). + */ + if (!test_bit(FUSE_I_FORCE_DIO, &fi->state)) + fuse_inode_uncached_io_end(fi); inode_unlock_shared(inode); } } @@ -1695,6 +1738,8 @@ static bool fuse_cache_wr_exclusive_lock(struct kiocb *iocb, bool cache_mode) return false; } +static ssize_t fuse_direct_write_iter(struct kiocb *iocb, struct iov_iter *from); + static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) { struct file *file = iocb->ki_filp; @@ -1708,6 +1753,17 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) bool writeback = false; bool cache_mode = false; bool exclusive; + bool wb_guard = false; + + /* + * The inode may have been latched into forced direct IO -- by a + * NOTIFY_INVAL_INODE arriving while this inode is open for writing here + * -- after this write was routed to the cached path but before it took + * any lock. Re-route to the direct path (before taking a DLM lock) so + * we do not repopulate the page cache the latch just dropped. + */ + if (fuse_inode_force_dio(inode)) + return fuse_direct_write_iter(iocb, from); if (fc->writeback_cache) { /* Update size (EOF optimization) and mode (SUID clearing) */ @@ -1753,6 +1809,30 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) else inode_lock_shared(inode); + /* + * The forced-direct-IO latch feature is active under writeback+dlm; + * hold wb_inval_rwsem for read across the page-cache dirtying so a + * concurrent NOTIFY_INVAL_INODE -- which latches the inode under the + * write side of this lock via a non-blocking trylock -- cannot strand + * the folios we are about to write. Re-check the latch under it (it may + * have been set while we blocked on the inode lock) and re-route to the + * direct path if set. Taken before task_io_account_write() so a + * re-route is not double-counted; the DLM write lock taken above is + * harmless as the direct path does its own server coordination. + */ + wb_guard = fc->writeback_cache && fc->dlm; + if (wb_guard) { + down_read(&fi->wb_inval_rwsem); + if (fuse_inode_force_dio(inode)) { + up_read(&fi->wb_inval_rwsem); + if (exclusive) + inode_unlock(inode); + else + inode_unlock_shared(inode); + return fuse_direct_write_iter(iocb, from); + } + } + err = count = generic_write_checks(iocb, from); if (err <= 0) goto out; @@ -1823,6 +1903,8 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) written = fuse_perform_write(iocb, from); } out: + if (wb_guard) + up_read(&fi->wb_inval_rwsem); if (exclusive) inode_unlock(inode); else @@ -2127,7 +2209,7 @@ static ssize_t fuse_file_read_iter(struct kiocb *iocb, struct iov_iter *to) return fuse_dax_read_iter(iocb, to); /* FOPEN_DIRECT_IO overrides FOPEN_PASSTHROUGH */ - if (ff->open_flags & FOPEN_DIRECT_IO) + if ((ff->open_flags & FOPEN_DIRECT_IO) || fuse_inode_force_dio(inode)) return fuse_direct_read_iter(iocb, to); else if (fuse_file_passthrough(ff)) return fuse_passthrough_read_iter(iocb, to); @@ -2148,7 +2230,7 @@ static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from) return fuse_dax_write_iter(iocb, from); /* FOPEN_DIRECT_IO overrides FOPEN_PASSTHROUGH */ - if (ff->open_flags & FOPEN_DIRECT_IO) + if ((ff->open_flags & FOPEN_DIRECT_IO) || fuse_inode_force_dio(inode)) return fuse_direct_write_iter(iocb, from); else if (fuse_file_passthrough(ff)) return fuse_passthrough_write_iter(iocb, from); @@ -2776,6 +2858,29 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma) else if (fuse_inode_backing(get_fuse_inode(inode))) return -ENODEV; + /* + * If the inode was latched into forced direct IO after a remote-modify + * notification, a mapping needs the page cache, so revert to caching + * mode. Revert without the inode lock or wb_inval_rwsem: ->mmap runs + * under mmap_lock and the buffered write path holds both across a fault + * on the user buffer (which takes mmap_lock), so taking either here + * would invert lock order (ABBA). Clearing the latch and dropping the + * cache is sufficient -- writers re-check the latch and route to cached + * IO once it is clear, and in-flight parallel dio drains itself. Cached + * opens frozen while latched are still counted in iocachectr, so restore + * FUSE_I_CACHE_IO_MODE for them. + */ + if (fuse_inode_force_dio(inode)) { + struct fuse_inode *fi = get_fuse_inode(inode); + + spin_lock(&fi->lock); + clear_bit(FUSE_I_FORCE_DIO, &fi->state); + if (fi->iocachectr > 0) + set_bit(FUSE_I_CACHE_IO_MODE, &fi->state); + spin_unlock(&fi->lock); + invalidate_inode_pages2(file->f_mapping); + } + /* * FOPEN_DIRECT_IO handling is special compared to O_DIRECT, * as does not allow MAP_SHARED mmap without FUSE_DIRECT_IO_ALLOW_MMAP. @@ -3587,6 +3692,9 @@ void fuse_init_file_inode(struct inode *inode, unsigned int flags) fi->iocachectr = 0; init_waitqueue_head(&fi->page_waitq); init_waitqueue_head(&fi->direct_io_waitq); + init_rwsem(&fi->wb_inval_rwsem); + fi->notify_stamp = jiffies; + fi->notify_interval_ewma = FUSE_NOTIFY_EWMA_SEED << FUSE_NOTIFY_EWMA_SHIFT; if (IS_ENABLED(CONFIG_FUSE_DAX)) fuse_dax_inode_init(inode, flags); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 365c28cb282146..dfa11e62cc00bd 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -119,6 +119,20 @@ struct dlm_locked_area size_t size; }; +/* + * Force-DIO switch trigger: an exponentially weighted moving average of the + * interval (in jiffies) between FUSE_NOTIFY_INVAL_INODE data invalidations for + * a file. When the average spacing falls below FUSE_NOTIFY_DIO_INTERVAL -- a + * remote writer streaming invalidations -- and the file is open for writing + * here, it is latched into direct IO. These are the source-level (not + * externally tunable) parameters of the heuristic: EWMA weight 1/2^SHIFT, + * seeded and capped at SEED so it takes a short burst rather than a single + * notify to trip. + */ +#define FUSE_NOTIFY_DIO_INTERVAL max_t(unsigned long, HZ / 10, 1) +#define FUSE_NOTIFY_EWMA_SHIFT 2 +#define FUSE_NOTIFY_EWMA_SEED (2 * FUSE_NOTIFY_DIO_INTERVAL) + /** FUSE inode */ struct fuse_inode { /** Inode data */ @@ -186,6 +200,34 @@ struct fuse_inode { /* dlm locked areas we have sent lock requests for */ struct fuse_dlm_cache dlm_locked_areas; + + /* + * Serializes buffered-write page-cache dirtying against + * the forced-direct-IO latch transition driven by + * NOTIFY_INVAL_INODE (fuse_reverse_inval_inode()), which + * may be delivered by the same server thread that still + * owes a reply to an in-flight write holding the inode + * lock. The buffered writer holds this for read around + * the dirtying and re-checks the latch under it; the + * NOTIFY latch site takes it for write (trylock, never + * blocking) around its page-cache invalidate + latch set. + * Only regular files initialise it -- it shares storage + * with the readdir-cache union arm. + */ + struct rw_semaphore wb_inval_rwsem; + + /* + * Rate of FUSE_NOTIFY_INVAL_INODE data invalidations + * for this whole file: notify_stamp is the jiffies of + * the last one, notify_interval_ewma the EWMA of the + * inter-arrival interval (jiffies, scaled by + * 2^FUSE_NOTIFY_EWMA_SHIFT). A rapid stream (short + * average interval) with a local writer latches the + * inode into direct IO. Protected by fi->lock; regular + * files only (shares the readdir-cache union arm). + */ + unsigned long notify_stamp; + unsigned int notify_interval_ewma; }; /* readdir cache (directory only) */ @@ -257,6 +299,14 @@ enum { FUSE_I_BTIME, /* Wants or already has page cache IO */ FUSE_I_CACHE_IO_MODE, + /* + * Latched into direct IO: a NOTIFY_INVAL_INODE arrived while the file + * was open for writing here, so another (remote) entity is modifying it + * concurrently. Reads and writes are routed direct (shared-lock + * parallel dio) until the last writer closes or the inode is mmapped. + * See fuse_reverse_inval_inode()/fuse_file_io_open(). + */ + FUSE_I_FORCE_DIO, }; struct fuse_conn; @@ -1600,6 +1650,12 @@ void fuse_inode_uncached_io_end(struct fuse_inode *fi); int fuse_file_io_open(struct file *file, struct inode *inode); void fuse_file_io_release(struct fuse_file *ff, struct inode *inode); +/* Inode latched into forced direct IO after a remote-modify notification */ +static inline bool fuse_inode_force_dio(struct inode *inode) +{ + return test_bit(FUSE_I_FORCE_DIO, &get_fuse_inode(inode)->state); +} + /* file.c */ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid, struct inode *inode, diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 6a94de9528210f..d057e1b9030a6a 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -757,6 +757,35 @@ static void fuse_invalidate_inode_entry(struct inode *inode) } } +/* + * Fold one FUSE_NOTIFY_INVAL_INODE data invalidation into the per-inode + * moving average of the notification inter-arrival interval and report whether + * the file is now "hot" -- notifications are arriving fast enough (short + * average interval) that a remote writer is repeatedly invalidating it. The + * average is an EWMA (weight 1/2^FUSE_NOTIFY_EWMA_SHIFT); the sample is clamped + * to FUSE_NOTIFY_EWMA_SEED so a notify after a long idle only cools the average + * and cannot overflow the accumulator. Must be called under fi->lock; called + * for every data invalidation so the average stays current even while no local + * writer is open. + */ +static bool fuse_notify_inval_hot(struct fuse_inode *fi) +{ + unsigned long now = jiffies; + unsigned long sample; + unsigned int avg; + + sample = min_t(unsigned long, now - fi->notify_stamp, + FUSE_NOTIFY_EWMA_SEED); + fi->notify_stamp = now; + + /* E += sample - (E >> SHIFT); avg = E >> SHIFT */ + fi->notify_interval_ewma += sample - + (fi->notify_interval_ewma >> FUSE_NOTIFY_EWMA_SHIFT); + avg = fi->notify_interval_ewma >> FUSE_NOTIFY_EWMA_SHIFT; + + return avg < FUSE_NOTIFY_DIO_INTERVAL; +} + int fuse_reverse_inval_inode(struct fuse_conn *fc, u64 nodeid, loff_t offset, loff_t len) { @@ -806,8 +835,70 @@ int fuse_reverse_inval_inode(struct fuse_conn *fc, u64 nodeid, pg_end == -1 ? 0 : (offset + len - 1)); - invalidate_inode_pages2_range(inode->i_mapping, - pg_start, pg_end); + /* + * A data invalidation means another (remote) entity is modifying + * the file. Keep a moving average of how fast these notifications + * arrive for the whole inode; when they come in a rapid stream -- + * a remote writer repeatedly invalidating the file -- and it is + * also open for writing here, latch the inode into direct IO: + * reads and writes are served direct (from the server) until the + * last writer closes or the inode is mmapped. A lone or occasional + * notify keeps the average high and does not trip the switch. Only + * under writeback+dlm, where the buffered write's RMW read is + * skipped so its wb_inval_rwsem read-side section is free of server + * round-trips. + * + * fuse_notify_inval_hot() updates the average under fi->lock and is + * called for every data invalidation so it stays current even while + * no local writer is open. When it trips, take wb_inval_rwsem for + * write so the buffered write path -- which holds it for read across + * its dirtying and re-checks the latch under it -- cannot strand + * dirty folios after the cache is dropped. Use a trylock and never + * block: this may run on the server thread that still owes an + * in-flight write (holding the inode lock) its reply, so blocking on + * the rwsem or the inode lock would deadlock. If the writer has + * gone, skip the latch this round (best effort); the invalidate + * still runs. Only regular files initialise the average and the + * rwsem (they share storage with the readdir-cache union arm), so + * gate on S_ISREG. When latched, drop the whole mapping rather than + * just the notified range, or dirty folios outside it would be + * invisible to the forced direct reads (stale read / lost write). + */ + if (S_ISREG(inode->i_mode) && fc->writeback_cache && fc->dlm && + !FUSE_IS_DAX(inode) && !fuse_inode_backing(fi) && + !mapping_mapped(inode->i_mapping)) { + bool hot, has_writer; + + spin_lock(&fi->lock); + hot = fuse_notify_inval_hot(fi); + has_writer = !list_empty(&fi->write_files); + spin_unlock(&fi->lock); + + if (hot && has_writer && !fuse_inode_force_dio(inode) && + down_write_trylock(&fi->wb_inval_rwsem)) { + bool latched = false; + + spin_lock(&fi->lock); + if (!list_empty(&fi->write_files)) { + set_bit(FUSE_I_FORCE_DIO, &fi->state); + latched = true; + } + spin_unlock(&fi->lock); + + if (latched) + invalidate_inode_pages2(inode->i_mapping); + else + invalidate_inode_pages2_range(inode->i_mapping, + pg_start, pg_end); + up_write(&fi->wb_inval_rwsem); + } else { + invalidate_inode_pages2_range(inode->i_mapping, + pg_start, pg_end); + } + } else { + invalidate_inode_pages2_range(inode->i_mapping, + pg_start, pg_end); + } } iput(inode); return 0; diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c index c99e285f3183ef..301294c8fb25f0 100644 --- a/fs/fuse/iomode.c +++ b/fs/fuse/iomode.c @@ -233,6 +233,16 @@ int fuse_file_io_open(struct file *file, struct inode *inode) !(ff->open_flags & FOPEN_PASSTHROUGH)) return 0; + /* + * The inode was latched into direct IO after a remote-modify + * notification arrived while it was open for writing here. Open this + * file uncached as well so its IO is routed direct and it does not + * re-enter caching mode. + */ + if (test_bit(FUSE_I_FORCE_DIO, &fi->state) && + !(ff->open_flags & FOPEN_PASSTHROUGH)) + return 0; + if (ff->open_flags & FOPEN_PASSTHROUGH) err = fuse_file_passthrough_open(inode, file); else