From 30657b62d867d0ca5619f44c0dc79c9a8cc9165f Mon Sep 17 00:00:00 2001 From: Zoltan Csizmadia Date: Sun, 13 Sep 2026 18:07:10 -0500 Subject: [PATCH] mount: stop running the page-cache punch on Windows, where it is a no-op WinFsp-FUSE's fuse_invalidate_path is a stub. inc/fuse3/fuse.h, unchanged from v2.0 through master: FSP_FUSE_SYM( int fuse3_invalidate_path(struct fuse3 *f, const char *path), { (void)f; (void)path; return -ENOENT; }) winfsp_fuse.h maps that spelling onto the standard one and FSP_FUSE_SYM expands to `static inline`, so the call never leaves the translation unit, never reaches the DLL, and invalidates nothing. So Direction A has been running a thread and a queue whose only purpose is to call a no-op a few thousand times per invalidation burst. Skip both there. Nothing is lost: the change-notification bridge that also reads the hook is Linux-only, and the punch was already doing nothing. The comment this replaces recorded that WinFsp "showed no difference from the page-cache change either way" and concluded it was free there. The measurement was right; the explanation was not. It is free because it does nothing, and leaving that unsaid implies Direction A has a cache coherence mechanism it does not have - which is the assumption behind two of the three fixes #88 has already tried and refuted. No fix for #88 here, and none is claimed. What WinFsp does offer is a mount option, -o FileInfoTimeout / -o DirInfoTimeout, which nothing has tried yet; the issue records why that is not worth changing while the flake is dormant and unmeasurable. Verified on Linux: 146/146 unit tests, 31/31 filesystem conformance, 12/12 change notification. The Windows path is compile-and-battery checked by CI. Refs #88 Co-Authored-By: Claude Opus 5 --- src/mount/fuse_mount.cpp | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/mount/fuse_mount.cpp b/src/mount/fuse_mount.cpp index c60b02b..4c0b807 100644 --- a/src/mount/fuse_mount.cpp +++ b/src/mount/fuse_mount.cpp @@ -617,9 +617,30 @@ Result FuseMount::mount(const std::string& mountpoint, bool writeback, boo // kernel has dropped the pages, and doing that on RemoteRoot's reader thread // would stall every other reply behind it. // - // The 981ms-to-5ms figure is a libfuse measurement; WinFsp showed no - // difference from the page-cache change either way, so this costs nothing - // measurable there and is left on for both. + // Linux only, because on Windows there is nothing to punch with. + // WinFsp-FUSE's fuse_invalidate_path is a stub — inc/fuse3/fuse.h, unchanged + // from v2.0 through master: + // + // FSP_FUSE_SYM( + // int fuse3_invalidate_path(struct fuse3 *f, const char *path), + // { + // (void)f; + // (void)path; + // return -ENOENT; + // }) + // + // winfsp_fuse.h maps that spelling onto the standard one and FSP_FUSE_SYM + // expands to `static inline`, so the call never leaves the translation unit, + // never reaches the DLL, and invalidates nothing. An earlier version of this + // comment recorded that WinFsp "showed no difference from the page-cache + // change either way" and concluded it was free there. The measurement was + // right and the conclusion was wrong: it is free because it does nothing. + // + // So Direction A gets a thread and a queue that exist only to call a no-op a + // few thousand times per burst. Skip both, and stop implying a coherence + // mechanism is running there when none is. What WinFsp does offer is a mount + // option, -o FileInfoTimeout / -o DirInfoTimeout; see #88. +#ifndef _WIN32 { std::lock_guard lock(inval_mu_); inval_stop_ = false; @@ -646,6 +667,7 @@ Result FuseMount::mount(const std::string& mountpoint, bool writeback, boo } notify_.post(changes); }); +#endif // !_WIN32 loop_ = std::thread([this] { // Deliberately single-threaded. fuse_loop_mt() has been measured against