From 5accfc9ddc7362bfad361d68bf982f7b332e59ac Mon Sep 17 00:00:00 2001 From: Horst Birthelmer Date: Wed, 7 Oct 2026 13:50:23 +0200 Subject: [PATCH 1/6] fuse: let the server stamp mtime and ctime in SETATTR under DLM Under DLM the server's time stamps are authoritative, fuse_get_cache_mask() returns 0 and the reply overwrites whatever fuse_do_setattr() set locally. Do not claim the local stamps in the request either: send FATTR_MTIME_NOW for truncate(2) and utimes(UTIME_NOW), and no mtime for ftruncate(2) and no ctime at all, as without the writeback cache. Signed-off-by: Horst Birthelmer --- fs/fuse/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 88f23acd541c13..1b2076130617a7 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -2092,7 +2092,7 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr, bool is_wb = fc->writeback_cache && S_ISREG(inode->i_mode); loff_t oldsize; int err; - bool trust_local_cmtime = is_wb; + bool trust_local_cmtime = is_wb && !fc->dlm; bool fault_blocked = false; u64 attr_version; From 69585bbefa0cd3f2a3a1754ff9f9af9a28b627e7 Mon Sep 17 00:00:00 2001 From: Horst Birthelmer Date: Wed, 14 Oct 2026 15:30:26 +0200 Subject: [PATCH 2/6] fuse: take the DLM grant for a page fault under writeback A page fault filled the page cache without a grant, so a remote write never invalidated the page. Ask for the grant before filemap_fault() runs, a write grant when the mapping is shared and writable. The page_mkwrite request stays for the writethrough case only. The fault must not block under mmap_lock: the read and write paths hold the coherency gate while faulting in their user buffers, which takes mmap_lock, so a fault blocking on the gate or the server with a pending mmap_lock writer queued deadlocks three ways. A fast path maps a resident page off a recorded grant under a gate trylock. Otherwise the fault lock is dropped, the grant is taken and the pages are read into private folios and installed under the gate, and the fault is retried. Only a fault that allows no retry blocks in place. Grant and read cover a 16-page window centred on the faulted page, so the neighbour of a spanning access maps without a server round trip whose revoke would drop the page just mapped. A resident page that is not uptodate is filled from the private read under a trylock, or the install would -EEXIST against it on every pass. Port of deb9a15c922c, bf37dc57bbd1, 9cdd0bae5a27 and 26d2a04d01bf from redfs-rhel10_0 (Linux 6.12). That branch fences the fault with the IO range lock; this one has the per-inode gate (fi->wb_inval_rwsem), under whose write side a revoke drops both the grant record and the pages, so a grant seen under the read side holds for the whole hold. Signed-off-by: Horst Birthelmer --- fs/fuse/file.c | 486 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 484 insertions(+), 2 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 83fb69070eb014..fdf3cd2eefedad 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -20,7 +20,9 @@ #include #include #include +#include #include +#include int sb_init_dio_done_wq(struct super_block *sb); @@ -2776,6 +2778,61 @@ static int fuse_get_page_mkwrite_lock(struct file *file, loff_t offset, size_t l } return err; } + +/* + * Batch size for fuse_fault_read_private_folios(): one fault-around + * window (64k with 4k pages), centred on the faulted page. A + * spanning access (one instruction touching the tail of one page and + * the head of the next, which any unaligned copy does constantly) + * needs BOTH pages mapped at once to retire, and every server round + * trip gets this client's grant revoked and its page cache dropped + * (the server does not exempt the requester, and the notification + * runs one op behind). A one-page grant-and-populate therefore + * ping-pongs forever: mapping each page costs a round trip whose + * revoke drops the other. A window covering both pages makes the + * neighbour's fault a pure fast-path hit (recorded grant, resident + * folio, NO server op and no new revoke) and the spanning access + * completes. Centred, not aligned: an aligned window has edges + * (every 16th page boundary), and a spanning access sitting on an + * edge gets two one-page-covering windows and ping-pongs just the + * same (generic/263); a centred window always covers both of the + * faulted page's neighbours, so no boundary has the problem. + */ +#define FUSE_FAULT_BATCH_PAGES 16 + +/* First page of the populate/grant window centred on @index. */ +static pgoff_t fuse_fault_batch_base(pgoff_t index) +{ + if (index < FUSE_FAULT_BATCH_PAGES / 2) + return 0; + return index - FUSE_FAULT_BATCH_PAGES / 2; +} + +/* + * The coherency gate (fi->wb_inval_rwsem) is the fault path's fence + * against a revoke: a NOTIFY invalidate and a local truncate revoke + * the grant and drop the pages under its write side, so a grant seen + * under the read side stays for the whole hold. The gate is NULL + * when its allocation failed; nothing fences the revoke on such an + * inode anywhere, so the helpers pass. + */ +static bool fuse_wb_gate_tryenter(struct percpu_rw_semaphore *sem) +{ + return !sem || percpu_down_read_trylock(sem); +} + +static void fuse_wb_gate_enter(struct percpu_rw_semaphore *sem) +{ + if (sem) + percpu_down_read(sem); +} + +static void fuse_wb_gate_exit(struct percpu_rw_semaphore *sem) +{ + if (sem) + percpu_up_read(sem); +} + /* * Wait for writeback against this page to complete before allowing it * to be marked dirty again, and hence written back again, possibly @@ -2798,7 +2855,7 @@ static vm_fault_t fuse_page_mkwrite(struct vm_fault *vmf) struct inode *inode = file_inode(file); struct fuse_mount *fm = get_fuse_mount(inode); - if (fm->fc->dlm) { + if (!fm->fc->writeback_cache && fm->fc->dlm) { loff_t pos = vmf->pgoff << PAGE_SHIFT; size_t length = PAGE_SIZE; int err = fuse_get_page_mkwrite_lock(file, pos, length); @@ -2818,9 +2875,434 @@ static vm_fault_t fuse_page_mkwrite(struct vm_fault *vmf) return VM_FAULT_LOCKED; } +/* + * Does filemap_fault() need a synchronous READ before it can map + * @index? True when the page is absent or not uptodate and the fault + * is inside i_size: past EOF, filemap_fault() returns SIGBUS before + * ever touching the page, so no READ can happen there. The lookup + * never blocks, so this is safe under mmap_lock (or the per-VMA lock) + * and with the gate in any state. + */ +static bool fuse_fault_needs_read(struct inode *inode, pgoff_t index) +{ + struct folio *folio; + bool uptodate; + + if (((loff_t)index << PAGE_SHIFT) >= i_size_read(inode)) + return false; + + folio = filemap_get_folio(inode->i_mapping, index); + if (IS_ERR(folio)) + return true; + + uptodate = folio_test_uptodate(folio); + folio_put(folio); + return !uptodate; +} + +/* + * Read a batch of pages centred on @index into freshly allocated + * folios that are NOT in the page cache. Unlike read_cache_folio(), + * nothing contested is held while the READ waits on the server: no + * page-cache folio lock (an invalidation laundering the range would + * block on it, while the server holds this READ behind the revoke + * that triggered the invalidation, the same cycle as the gate one, + * one lock over), and the caller is outside the gate. The folios are + * returned with a reference each and no mapping; the caller installs + * them with fuse_fault_install_folio() under the gate. + * + * A short read (EOF) leaves the tails zeroed (page_zeroing), which is + * what the page cache stores for the EOF pages anyway. i_size is + * deliberately NOT shrunk here (no fuse_short_read()), to keep this + * reader free of attribute side effects. + * + * On success returns the number of folios in @folios (>= 1, @index + * always covered) and sets @basep to the first folio's page index; + * negative error otherwise. + */ +static int fuse_fault_read_private_folios(struct file *file, + struct inode *inode, + pgoff_t index, pgoff_t *basep, + struct folio **folios) +{ + struct fuse_mount *fm = get_fuse_mount(inode); + struct fuse_page_desc descs[FUSE_FAULT_BATCH_PAGES]; + struct page *pages[FUSE_FAULT_BATCH_PAGES]; + struct fuse_io_args ia = { + .ap.args.page_zeroing = true, + .ap.args.out_pages = true, + .ap.descs = descs, + .ap.pages = pages, + }; + pgoff_t base = fuse_fault_batch_base(index); + loff_t isize = i_size_read(inode); + loff_t pos = (loff_t)base << PAGE_SHIFT; + pgoff_t last = (isize - 1) >> PAGE_SHIFT; + unsigned int nr, i; + size_t count; + ssize_t res; + + /* Caller checked index < i_size; clip the window to EOF. */ + nr = min_t(pgoff_t, FUSE_FAULT_BATCH_PAGES, last - base + 1); + + for (i = 0; i < nr; i++) { + folios[i] = filemap_alloc_folio( + mapping_gfp_mask(inode->i_mapping), 0); + if (!folios[i]) + break; + pages[i] = &folios[i]->page; + descs[i].offset = 0; + descs[i].length = PAGE_SIZE; + } + if (i <= index - base) { + /* Not even the faulted page: fail with what we freed. */ + while (i--) + folio_put(folios[i]); + return -ENOMEM; + } + nr = i; + ia.ap.num_pages = nr; + count = (size_t)nr << PAGE_SHIFT; + + /* Don't overflow end offset */ + if (pos + (count - 1) == LLONG_MAX) { + count--; + descs[nr - 1].length--; + } + + fuse_read_args_fill(&ia, file, pos, count, FUSE_READ); + res = fuse_simple_request(fm, &ia.ap.args); + if (res < 0) { + for (i = 0; i < nr; i++) + folio_put(folios[i]); + return res; + } + + *basep = base; + return nr; +} + +/* + * Install a folio filled by fuse_fault_read_private_folios() into the + * page cache. Only called under the gate. filemap_add_folio() + * returns the folio locked; it is held only over the uptodate + * marking, never over a server wait. If another path populated the + * index meanwhile (-EEXIST) with an uptodate folio, that copy is just + * as good. A resident !uptodate folio, though, may have NO read in + * flight to ever complete it: an errored readahead (the server + * bounces READs behind a revoke in flight) leaves exactly that: + * unlocked, resident, !uptodate. The buffered read path would lock + * and re-read it, but this path reads privately and would -EEXIST + * against it on every pass, and the caller's residency re-check would + * loop forever (the generic/120 livelock). So fill it from the + * just-read data under a trylock: trylock never waits (no server wait + * is allowed under the gate), and losing the trylock means a read IS + * in flight, which resolves the index on its own. Does not consume + * the caller's reference. + * + * No freshness test gates the install, deliberately. The server + * revokes this client's own grant to serve the private READ (it does + * not exempt the requester), so the READ's own revoke notification is + * in flight more or less whenever this is called, and any "nothing + * invalidated this inode across the READ window" check fails on every + * pass: the probe destroys what it measures, and the fault livelocks + * re-reading forever. Instead the caller holds the gate from here + * through filemap_fault(), so the notification (which takes the gate + * write side to drop pages) cannot remove the folio before the PTE is + * mapped. A revoke that lands after the release drops the folio and + * unmaps the PTE, and the next access refaults and re-reads: coherent + * at the protocol's notification-latency granularity, same as every + * other cached page. + */ +static void fuse_fault_install_folio(struct inode *inode, + struct folio *folio, pgoff_t index) +{ + struct folio *stale; + + if (!filemap_add_folio(inode->i_mapping, folio, index, + mapping_gfp_mask(inode->i_mapping))) { + folio_mark_uptodate(folio); + folio_unlock(folio); + return; + } + + stale = filemap_get_folio(inode->i_mapping, index); + if (IS_ERR(stale)) + return; + if (!folio_test_uptodate(stale) && !folio_test_large(stale) && + folio_trylock(stale)) { + /* Re-check under the lock; skip if truncated away. */ + if (!folio_test_uptodate(stale) && + stale->mapping == inode->i_mapping) { + void *src = kmap_local_folio(folio, 0); + void *dst = kmap_local_folio(stale, 0); + + memcpy(dst, src, PAGE_SIZE); + kunmap_local(dst); + kunmap_local(src); + flush_dcache_folio(stale); + folio_mark_uptodate(stale); + } + folio_unlock(stale); + } + folio_put(stale); +} + +/* + * Install the folios a private read returned, under the gate, and + * drop the caller's references. + */ +static void fuse_fault_install_folios(struct inode *inode, + struct folio **folios, int nr, + pgoff_t base) +{ + int i; + + for (i = 0; i < nr; i++) { + fuse_fault_install_folio(inode, folios[i], base + i); + folio_put(folios[i]); + } +} + +static vm_fault_t fuse_filemap_fault(struct vm_fault *vmf) +{ + struct file *file = vmf->vma->vm_file; + struct inode *inode = file_inode(file); + struct fuse_conn *fc = get_fuse_conn(inode); + + if (fc->writeback_cache && fc->dlm) { + struct fuse_inode *fi = get_fuse_inode(inode); + struct percpu_rw_semaphore *wb_sem = fi->wb_inval_rwsem; + loff_t pos = vmf->pgoff << PAGE_SHIFT; + /* + * Grant and populate in whole batch windows. A one-page + * grant breaks spanning accesses (one instruction + * touching two pages): the neighbour's fault finds no + * recorded grant, and its own fuse_get_dlm_lock() round + * trip costs a revoke that drops the page just mapped, + * a deterministic ping-pong (the generic/091 livelock). + * With the grant covering the same window the populate + * read fills, the neighbour's fault passes the fast + * path's is_held() and residency checks and maps with no + * server op at all. + */ + loff_t bpos = (loff_t)fuse_fault_batch_base(vmf->pgoff) + << PAGE_SHIFT; + size_t blen = (size_t)FUSE_FAULT_BATCH_PAGES << PAGE_SHIFT; + enum fuse_page_lock_mode mode = FUSE_PAGE_LOCK_READ; + struct folio *folios[FUSE_FAULT_BATCH_PAGES]; + pgoff_t fbase; + vm_fault_t ret; + int tries; + int err; + int nr; + + if ((vmf->vma->vm_flags & (VM_SHARED | VM_MAYWRITE)) == + (VM_SHARED | VM_MAYWRITE)) + mode = FUSE_PAGE_LOCK_WRITE; + + /* + * Fast path: gate uncontended, the grant already + * recorded, and the folio already resident. Nothing here + * blocks, so it is safe under mmap_lock (or the per-VMA + * lock). Gate first, then is_held(): only under the + * gate, which fences any concurrent revoke, can the + * recorded grant be trusted, the same order as the + * buffered read and write paths. The folio check keeps + * filemap_fault() from issuing a synchronous READ under + * the gate: nothing held under it may wait on the server + * (see the blocking path below), so a missing folio falls + * through to the slow path, which populates it outside. + */ + if (fuse_wb_gate_tryenter(wb_sem)) { + if (fuse_dlm_lock_is_held(fi, pos, PAGE_SIZE, mode) && + !fuse_fault_needs_read(inode, vmf->pgoff)) { + ret = filemap_fault(vmf); + fuse_wb_gate_exit(wb_sem); + return ret; + } + fuse_wb_gate_exit(wb_sem); + } + + /* + * Slow path: the gate or the grant needs waiting, an + * invalidation draining the inode or a DLM round trip to + * the server. Blocking here while holding mmap_lock + * recreates the ABBA the read/write paths set up the + * other way around: they hold the gate while faulting in + * their user buffers, which takes mmap_lock. Drop the + * fault lock first, do the blocking work with only a file + * reference pinning the inode, then ask for the fault to + * be retried; the retry takes the fast path above off the + * now-recorded grant. The gate is not held across the + * retry (there would be nowhere to release it), so the + * retried fault can still miss and come back here; each + * pass leaves a recorded grant behind, which only a real + * invalidation takes away again. + * + * FAULT_FLAG_TRIED does not demote a pass to the blocking + * path below: retries may repeat (4064b98 "mm: allow + * VM_FAULT_RETRY for multiple times"), and blocking on + * the gate with mmap_lock held closes a three-party cycle + * whenever the gate reader is a buffered write faulting + * in its source buffer (which waits on mmap_lock) behind + * a queued mmap_lock writer. A miss on the retried fast + * path only means the gate is contended again; waiting + * for it must happen with mmap_lock dropped, every time. + */ + if (vmf->flags & FAULT_FLAG_ALLOW_RETRY) { + if (vmf->flags & FAULT_FLAG_RETRY_NOWAIT) + return VM_FAULT_RETRY; + + file = get_file(file); + release_fault_lock(vmf); + + /* + * A hard error is not failed here, it cannot be + * with the fault lock dropped: the retried fault + * meets it again. + */ + fuse_get_dlm_lock(file, bpos, blen, mode); + /* + * Populate the folios too, or the retried fault's + * fast path would refuse the grant it just + * recorded (missing folio) and come straight back + * here. Read outside the gate, install under it; + * see the blocking path below. + */ + if (fuse_fault_needs_read(inode, vmf->pgoff)) { + nr = fuse_fault_read_private_folios( + file, inode, vmf->pgoff, &fbase, + folios); + if (nr > 0) { + fuse_wb_gate_enter(wb_sem); + fuse_fault_install_folios(inode, folios, + nr, fbase); + fuse_wb_gate_exit(wb_sem); + } + } + fput(file); + return VM_FAULT_RETRY; + } + + /* + * Retry not allowed (a caller without + * FAULT_FLAG_ALLOW_RETRY): block in place. It can stack + * behind a pending mmap_lock writer the way any blocking + * fault can. + * + * Nothing held under the gate may wait for the server + * (see fuse_cache_read_iter), and filemap_fault() on a + * non-resident folio does exactly that: a synchronous + * READ the server can queue behind a revoke already in + * flight for this range, whose invalidation then waits + * on the gate. ABBA through the server (the generic/091 + * hang). Nor can the page-cache read path be used with + * the gate merely dropped: it keeps the folio locked in + * the mapping across the READ, and the invalidation + * blocks on that folio lock instead, the same cycle one + * lock over. So read into private folios outside the + * gate (nothing the invalidation needs is held), then + * enter it and install them, and hold the gate from the + * install through filemap_fault() so the READ's own + * revoke notification (the server revokes the requester's + * grant to serve the READ) cannot drop the folio before + * the PTE is mapped; see fuse_fault_install_folio(). + */ + for (tries = 0; ; tries++) { + err = fuse_get_dlm_lock(file, bpos, blen, mode); + if (err < 0 && err != -ENOSYS) + return vmf_error(err); + + fuse_wb_gate_enter(wb_sem); + /* + * A pass loops only when the grant or the folio + * went missing again between the previous pass + * and this one (a revoke landed in the gap), or + * on a bounced READ. Each successful install + * exits the loop under the gate, so a storm costs + * refaults, not loop passes; still killable in + * case the server bounces READs indefinitely. + */ + if (fatal_signal_pending(current)) { + fuse_wb_gate_exit(wb_sem); + return vmf_error(-EINTR); + } + if (tries && (tries % 64) == 0) + pr_warn_ratelimited("fuse: fault populate slow, nodeid %llu idx %lu: %d passes\n", + fi->nodeid, vmf->pgoff, + tries); + + /* + * The request ran outside the gate and the + * invalidate just waited on revokes under its + * write side: re-validate, as the IO paths do. + * An unrecorded grant (err > 0) cannot show here + * and is not re-requested. + */ + if (!err && fc->dlm && + !fuse_dlm_lock_is_held(fi, pos, PAGE_SIZE, mode)) { + fuse_wb_gate_exit(wb_sem); + continue; + } + + if (!fuse_fault_needs_read(inode, vmf->pgoff)) + break; + + fuse_wb_gate_exit(wb_sem); + nr = fuse_fault_read_private_folios(file, inode, + vmf->pgoff, + &fbase, folios); + if (nr < 0) { + /* + * -EDEADLK/-EAGAIN is the server bouncing + * a READ it cannot serve yet (a revoke in + * flight); back off and retry, the same + * thing AOP_TRUNCATED_PAGE does for the + * page-cache read path. Anything else is + * a real read error: let filemap_fault() + * run and turn it into SIGBUS; the server + * answers (with the error) rather than + * queueing, so nothing hangs. + */ + if (nr == -EDEADLK || nr == -EAGAIN) { + msleep_interruptible(2); + continue; + } + fuse_wb_gate_enter(wb_sem); + break; + } + + fuse_wb_gate_enter(wb_sem); + fuse_fault_install_folios(inode, folios, nr, fbase); + /* + * Exit under the gate: filemap_fault() below maps + * the just-installed folio without a server wait + * (present and uptodate), and the gate keeps any + * pending revoke's page drop out until the PTE is + * in place. If the install lost to -EEXIST on a + * !uptodate foreign folio it could not fill (a + * read in flight holds the folio lock), loop + * instead: mapping it would trigger a + * synchronous READ under the gate, and the + * in-flight read resolves the index anyway. + */ + if (!fuse_fault_needs_read(inode, vmf->pgoff)) + break; + fuse_wb_gate_exit(wb_sem); + } + + ret = filemap_fault(vmf); + fuse_wb_gate_exit(wb_sem); + return ret; + } + + return filemap_fault(vmf); +} + static const struct vm_operations_struct fuse_file_vm_ops = { .close = fuse_vma_close, - .fault = filemap_fault, + .fault = fuse_filemap_fault, .map_pages = filemap_map_pages, .page_mkwrite = fuse_page_mkwrite, }; From db72337081b9dab55db29645ba3131beebbae2ca Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Wed, 14 Oct 2026 15:30:26 +0200 Subject: [PATCH 3/6] fuse: drop fault-around for shared-writable mappings under DLM With the writeback cache on, page_mkwrite skipped the DLM lock request, assuming fuse_filemap_fault() had taken a write grant for any page a shared-writable mapping could dirty. Fault-around (filemap_map_pages) PTE-maps any resident folio in its window without calling ->fault, so a folio read in under a READ grant takes its first write fault with no WRITE grant ever requested, and writeback sends a WRITE the server sees under a read lock. page_mkwrite cannot take the grant itself: it runs with mmap_lock (or the per-VMA lock) held and has no VM_FAULT_RETRY protocol to drop it, while the buffered IO paths hold the coherency gate while faulting in their user buffers. Give shared-writable VMAs under the writeback cache with DLM a vm_operations_struct without ->map_pages, so the first touch of every page goes through fuse_filemap_fault(). page_mkwrite keeps a non-blocking check of the recorded grant under a gate trylock and returns VM_FAULT_NOPAGE on a miss: a verified miss means a revoke or truncate in flight whose page drop clears the PTE, and a contended trylock re-faults with mmap_lock released in between. Port of 52b0cfbd0921 from redfs-rhel10_0 (Linux 6.12); the gate trylock replaces the range lock trylock. Reported-by: Hai Zhong Zhou Suggested-by: Yong Ze Chen Signed-off-by: Allison Henderson Signed-off-by: Horst Birthelmer --- fs/fuse/file.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index fdf3cd2eefedad..f600053f3c0451 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -2854,9 +2854,54 @@ static vm_fault_t fuse_page_mkwrite(struct vm_fault *vmf) struct file *file = vmf->vma->vm_file; struct inode *inode = file_inode(file); struct fuse_mount *fm = get_fuse_mount(inode); + loff_t pos = vmf->pgoff << PAGE_SHIFT; - if (!fm->fc->writeback_cache && fm->fc->dlm) { - loff_t pos = vmf->pgoff << PAGE_SHIFT; + if (fm->fc->writeback_cache && fm->fc->dlm) { + /* + * Writenotify routes every clean-to-dirty transition of a + * shared mapping through here, but the WRITE grant itself + * is taken by fuse_filemap_fault(): shared-writable VMAs + * get no ->map_pages under DLM (see fuse_file_mmap()), so + * every PTE in such a VMA was installed by a fault that + * recorded a WRITE grant for its batch window. Only + * verify here, and only without blocking: nothing may + * sleep on the gate or the server while mmap_lock (or the + * per-VMA lock) is held. The buffered read and write + * paths hold the gate while faulting in their user + * buffers (which takes mmap_lock), so with a pending + * mmap_lock writer queued between the two, blocking here + * deadlocks three ways, the cycle fuse_filemap_fault() + * avoids with its drop-and-retry, and page_mkwrite has no + * VM_FAULT_RETRY protocol to drop the lock and come back + * with. + * + * Same fence as fuse_filemap_fault()'s fast path: only + * under the gate can the recorded grant be trusted. On a + * miss, NOPAGE. A contended trylock re-faults with + * mmap_lock released in between, so the gate writer can + * make progress; a verified miss means a revoke or a + * truncate is in flight (only those remove a recorded + * grant, and both drop the pages under the gate write + * side), whose unmap clears the PTE, so the re-fault goes + * through fuse_filemap_fault(), which re-takes the grant + * with the proper drop-and-retry dance. + */ + struct fuse_inode *fi = get_fuse_inode(inode); + struct percpu_rw_semaphore *wb_sem = fi->wb_inval_rwsem; + bool held = false; + + if (fuse_wb_gate_tryenter(wb_sem)) { + held = fuse_dlm_lock_is_held(fi, pos, PAGE_SIZE, + FUSE_PAGE_LOCK_WRITE); + if (!held) + pr_warn_ratelimited("fuse: mkwrite with no WRITE grant, nodeid %llu idx %lu\n", + fi->nodeid, vmf->pgoff); + fuse_wb_gate_exit(wb_sem); + } + + if (!held) + return VM_FAULT_NOPAGE; + } else if (fm->fc->dlm) { size_t length = PAGE_SIZE; int err = fuse_get_page_mkwrite_lock(file, pos, length); if (err < 0) { @@ -3307,6 +3352,23 @@ static const struct vm_operations_struct fuse_file_vm_ops = { .page_mkwrite = fuse_page_mkwrite, }; +/* + * Shared-writable VMAs under the writeback cache with DLM: no + * ->map_pages. Fault-around installs PTEs for any resident folio in + * its window without calling ->fault, so a folio read in under a READ + * grant (read(2), readahead) could take its first write in + * ->page_mkwrite with no WRITE grant ever requested, and page_mkwrite + * cannot take one there (no retry protocol; see fuse_page_mkwrite()). + * Without fault-around, every first touch goes through + * fuse_filemap_fault(), which records a WRITE grant for the batch + * window before any PTE is installed. + */ +static const struct vm_operations_struct fuse_file_wb_dlm_vm_ops = { + .close = fuse_vma_close, + .fault = fuse_filemap_fault, + .page_mkwrite = fuse_page_mkwrite, +}; + static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma) { struct fuse_file *ff = file->private_data; @@ -3373,7 +3435,11 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma) fuse_link_write_file(file); file_accessed(file); - vma->vm_ops = &fuse_file_vm_ops; + if (fc->writeback_cache && fc->dlm && + (vma->vm_flags & VM_SHARED) && (vma->vm_flags & VM_MAYWRITE)) + vma->vm_ops = &fuse_file_wb_dlm_vm_ops; + else + vma->vm_ops = &fuse_file_vm_ops; return 0; } From b741c6164421490d86e14fd071b3243aa31991d1 Mon Sep 17 00:00:00 2001 From: Sven Oehme Date: Wed, 14 Oct 2026 15:30:26 +0200 Subject: [PATCH 4/6] fuse: SIGBUS a DLM fault on an aborted connection instead of retrying With the writeback cache and DLM, fuse_filemap_fault()'s slow path drops the fault lock, takes the DLM grant and reads the batch window into private folios, and returns VM_FAULT_RETRY. Errors on that path cannot be reported: once the fault lock is dropped, VM_FAULT_RETRY is the only answer the fault can give, and a retried fault that keeps failing comes straight back. An aborted connection fails every request, so a task touching a non-resident page of a mapping on that mount refaults forever. It spends all its time in the kernel, ignores every signal but SIGKILL, and its mapping keeps the mount busy, so the mount cannot be unmounted and recovered. Check the connection on the slow path while the fault lock is still held, and return VM_FAULT_SIGBUS when it is gone, which is what a fault on a non-resident page of an aborted mount returns without DLM. Resident pages with a recorded grant still map through the fast path. A persistent error on a live connection still loops the same way. Port of ae0e8f94a4bf from redfs-rhel10_0 (Linux 6.12); the hunk is identical. Signed-off-by: Sven Oehme Signed-off-by: Horst Birthelmer --- fs/fuse/file.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index f600053f3c0451..a5a9f6a2a1bd45 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -3194,7 +3194,20 @@ static vm_fault_t fuse_filemap_fault(struct vm_fault *vmf) * a queued mmap_lock writer. A miss on the retried fast * path only means the gate is contended again; waiting * for it must happen with mmap_lock dropped, every time. + * + * The flip side is that an error met with mmap_lock + * dropped cannot be reported: only VM_FAULT_RETRY (or + * COMPLETED) may be returned then, and a retried fault + * that keeps failing comes straight back here. An + * aborted connection fails every request, so the task + * would refault forever: it burns CPU in this path, + * only SIGKILL ends it, and its mapping keeps the mount + * busy. Check it while the fault lock is still held, + * where SIGBUS can be returned. */ + if (!READ_ONCE(fc->connected)) + return VM_FAULT_SIGBUS; + if (vmf->flags & FAULT_FLAG_ALLOW_RETRY) { if (vmf->flags & FAULT_FLAG_RETRY_NOWAIT) return VM_FAULT_RETRY; @@ -3205,7 +3218,8 @@ static vm_fault_t fuse_filemap_fault(struct vm_fault *vmf) /* * A hard error is not failed here, it cannot be * with the fault lock dropped: the retried fault - * meets it again. + * meets it again. A lost connection ends at the + * check above. */ fuse_get_dlm_lock(file, bpos, blen, mode); /* From b5d1c70152a0e984c5ba5c6d68898c4106984dc3 Mon Sep 17 00:00:00 2001 From: Allison Henderson Date: Wed, 14 Oct 2026 15:30:26 +0200 Subject: [PATCH 5/6] fuse: fault in the read buffer outside the coherency gate Nothing held under the coherency gate may wait for the server, but the cached read path could: generic_file_read_iter() faults on the user buffer during copy-out, and a buffer backed by a shared-writable mapping of a fuse file sends fuse_filemap_fault() after a WRITE grant. Granting that can first require the server to revoke the reading node's own READ grant, and the revoke's invalidation waits on the gate the reader holds: an ABBA with the server in the middle. A pread() into a shared-writable mapping of the same byte range reproduces it, and the victim sits in request_wait_answer(), which no longer dies to SIGKILL once the request has reached userspace. Copy with page faults disabled instead, so a non-resident user page makes the copy come back short rather than fault, then drop the gate, fault the buffer in, re-cover the grant (it may have been revoked while the gate was dropped), re-enter and resume. The re-entry re-validates the grant and the forced-DIO latch as the first pass does. Same shape as gfs2_file_read_iter() around its glock. The write path is not changed: its WRITE grant covers the IO range, so a fault on a source buffer overlapping the destination finds the grant recorded. A source buffer backed by a different, ungranted fuse range can still hit the same ABBA through generic_perform_write()'s fault-in and is left for a follow-up. Port of b7bc973416bd from redfs-rhel10_0 (Linux 6.12); the gate drop and re-entry replace the range lock INIT and LOCKED transitions. Signed-off-by: Allison Henderson Signed-off-by: Horst Birthelmer --- fs/fuse/file.c | 69 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index a5a9f6a2a1bd45..0fef45e8d5035a 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -23,6 +23,8 @@ #include #include #include +#include +#include int sb_init_dio_done_wq(struct super_block *sb); @@ -1211,6 +1213,9 @@ static ssize_t fuse_cache_read_iter(struct kiocb *iocb, struct iov_iter *to) struct fuse_inode *fi = get_fuse_inode(inode); struct percpu_rw_semaphore *wb_sem = fi->wb_inval_rwsem; size_t count = iov_iter_count(to); + int tries = FUSE_DLM_READ_RETRIES; + size_t prev_count = 0; + ssize_t total = 0; ssize_t res; int lock_err = 0; @@ -1238,6 +1243,10 @@ static ssize_t fuse_cache_read_iter(struct kiocb *iocb, struct iov_iter *to) iov_iter_count(to), FUSE_PAGE_LOCK_READ); + /* wb_sem is NULL on non-writeback+dlm mounts (gate inactive). */ + if (!wb_sem) + return generic_file_read_iter(iocb, to); + /* * Fence the cache-serving read against a NOTIFY invalidate so we never * hand back a folio the server has just superseded. The gate read side @@ -1245,16 +1254,34 @@ static ssize_t fuse_cache_read_iter(struct kiocb *iocb, struct iov_iter *to) * Re-check the forced-DIO latch under it: if a storm latched us while we * waited on a pending writer, reroute to direct like the buffered write * path, so we do not repopulate the cache the latch just dropped. - * wb_sem is NULL on non-writeback+dlm mounts (gate inactive). + * + * Nothing held under the gate may wait for the server, and faulting + * on the user buffer can do exactly that: a buffer backed by a + * shared-writable mapping of this (or any) fuse file sends + * fuse_filemap_fault() after a WRITE grant, and granting it can + * first require the server to revoke this read's own READ grant, + * whose invalidation then waits on the gate this read holds. ABBA + * through the server, and request_wait_answer() does not die to + * SIGKILL once the request is in userspace. + * + * So copy with page faults disabled (a non-resident user page makes + * the copy come back short instead of faulting), and fault the + * buffer in with the gate dropped: nothing is held then that the + * revoke's invalidation needs. The grant may be revoked while the + * gate is dropped, so re-cover it before re-entering, which + * re-validates it like the first pass. Same shape as + * gfs2_file_read_iter() around its glock. */ - if (wb_sem) { - int tries = FUSE_DLM_READ_RETRIES; + for (;;) { + size_t remain; + size_t window; retry: percpu_down_read(wb_sem); if (fuse_inode_force_dio(inode)) { percpu_up_read(wb_sem); - return fuse_direct_read_iter(iocb, to); + res = fuse_direct_read_iter(iocb, to); + break; } /* * The DLM lock was requested before entering the gate, and @@ -1267,8 +1294,8 @@ static ssize_t fuse_cache_read_iter(struct kiocb *iocb, struct iov_iter *to) * hold. A failed or unrecorded request falls through * unlocked, as before: the retry is taken even then (the * latch must be re-checked under the re-entered gate), so - * lock_err has to stay sticky across it -- seeded by the - * pre-gate request above -- or a grant that failed would + * lock_err has to stay sticky across it, seeded by the + * pre-gate request above, or a grant that failed would * be re-requested forever. The retry is also bounded: a * remote writer can revoke each successful grant before * the gate is re-entered, and a reader-only inode has no @@ -1286,14 +1313,34 @@ static ssize_t fuse_cache_read_iter(struct kiocb *iocb, struct iov_iter *to) FUSE_PAGE_LOCK_READ); goto retry; } - } - res = generic_file_read_iter(iocb, to); - - if (wb_sem) + pagefault_disable(); + res = generic_file_read_iter(iocb, to); + pagefault_enable(); percpu_up_read(wb_sem); + if (res > 0) + total += res; - return res; + if (res != -EFAULT && + !(res >= 0 && iov_iter_count(to) && + iocb->ki_pos < i_size_read(inode))) + break; + if (!user_backed_iter(to) || + iov_iter_count(to) == prev_count) + break; + prev_count = iov_iter_count(to); + + window = min_t(size_t, iov_iter_count(to), SZ_1M); + window -= fault_in_iov_iter_writeable(to, window); + if (!window) + break; + remain = iov_iter_count(to); + if (!lock_err && fc->dlm) + lock_err = fuse_get_dlm_lock(file, iocb->ki_pos, remain, + FUSE_PAGE_LOCK_READ); + } + + return total > 0 ? total : res; } static void fuse_write_args_fill(struct fuse_io_args *ia, struct fuse_file *ff, From 4253ed07c2e14fafd91e7bb47fcba2978efc1ef6 Mon Sep 17 00:00:00 2001 From: Sven Oehme Date: Wed, 14 Oct 2026 15:30:26 +0200 Subject: [PATCH 6/6] fuse: retry a short cached read one page at a time SPECsfs warmup reads on a write-back fuse mount returned EFAULT from fuse_cache_read_iter(). The copy runs with page faults disabled, then faults the user buffer in with the gate dropped. The loop gives up the first time a pass copies nothing, so one large fault-in that does not stick becomes EFAULT. Netmist reported that as platform error 14. gfs2's should_fault_in_pages() drops to a single page when a pass makes no progress. Its buffered read retries for as long as fault-in faults a byte, which spins if the page is already resident and the copy still returns -EFAULT. Stop after one single-page retry and warn. Port of 1faf5847b481 from redfs-rhel10_0 (Linux 6.12), itself a pick of 44fdde847d48 from redfs-ubuntu-hwe-6.17.0-16.16-24.04.1. Signed-off-by: Sven Oehme Signed-off-by: Horst Birthelmer --- fs/fuse/file.c | 52 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 0fef45e8d5035a..a848956b892af5 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1205,6 +1205,32 @@ static bool fuse_read_needs_size(struct inode *inode, loff_t pos, size_t len) return fuse_size_needs_server(inode) && pos + len > i_size_read(inode); } +/* + * How much of the user buffer to fault in before retrying a cached read. + * + * While the read is still making progress, fault a multi-page window, + * capped the way gfs2's should_fault_in_pages() does. After a retry + * copies nothing, drop to a single page so one large fault-in that does + * not stick gets a smaller try. The caller stops if that page also makes + * no progress: fault_in of an already resident page succeeds, so retrying + * it without a stop would spin. + */ +static size_t fuse_read_fault_window(struct kiocb *iocb, size_t count, + bool single_page) +{ + size_t size = PAGE_SIZE; + size_t offs = offset_in_page(iocb->ki_pos); + size_t nr_dirtied; + + if (!single_page) { + nr_dirtied = max(current->nr_dirtied_pause - + current->nr_dirtied, 8); + size = min_t(size_t, SZ_1M, nr_dirtied << PAGE_SHIFT); + } + + return min_t(size_t, size - offs, count); +} + static ssize_t fuse_cache_read_iter(struct kiocb *iocb, struct iov_iter *to) { struct file *file = iocb->ki_filp; @@ -1215,6 +1241,7 @@ static ssize_t fuse_cache_read_iter(struct kiocb *iocb, struct iov_iter *to) size_t count = iov_iter_count(to); int tries = FUSE_DLM_READ_RETRIES; size_t prev_count = 0; + bool single_page = false; ssize_t total = 0; ssize_t res; int lock_err = 0; @@ -1325,16 +1352,31 @@ static ssize_t fuse_cache_read_iter(struct kiocb *iocb, struct iov_iter *to) !(res >= 0 && iov_iter_count(to) && iocb->ki_pos < i_size_read(inode))) break; - if (!user_backed_iter(to) || - iov_iter_count(to) == prev_count) + if (!user_backed_iter(to)) + break; + + remain = iov_iter_count(to); + if (!remain) break; - prev_count = iov_iter_count(to); + if (remain == prev_count) { + if (single_page) { + pr_warn_ratelimited("fuse: cached read made no progress after a one-page fault-in, nodeid %llu pos %lld size %lld remain %zu res %zd\n", + fi->nodeid, iocb->ki_pos, + i_size_read(inode), remain, res); + break; + } + single_page = true; + } else { + single_page = false; + } + prev_count = remain; - window = min_t(size_t, iov_iter_count(to), SZ_1M); + window = fuse_read_fault_window(iocb, remain, single_page); + if (!window) + break; window -= fault_in_iov_iter_writeable(to, window); if (!window) break; - remain = iov_iter_count(to); if (!lock_err && fc->dlm) lock_err = fuse_get_dlm_lock(file, iocb->ki_pos, remain, FUSE_PAGE_LOCK_READ);