From: Aaron Paterson <apaterson@pm.me>
To: Miklos Szeredi <miklos@szeredi.hu>,
Stefan Hajnoczi <stefanha@redhat.com>,
Vivek Goyal <vgoyal@redhat.com>,
German Maglione <gmaglione@redhat.com>,
Shuah Khan <shuah@kernel.org>
Cc: "Eugenio Pérez" <eperezma@redhat.com>,
fuse-devel@lists.linux.dev, linux-fsdevel@vger.kernel.org,
linux-kselftest@vger.kernel.org, virtualization@lists.linux.dev,
linux-kernel@vger.kernel.org, "Aaron Paterson" <apaterson@pm.me>
Subject: [PATCH 0/5] fuse: report a request refused for a live nodeid as stale
Date: Thu, 27 Aug 2026 23:37:24 +0000 [thread overview]
Message-ID: <cover.1787873791.git.apaterson@pm.me> (raw)
A FUSE request that names a nodeid and nothing else is only sent for an
inode the client has looked up and holds a reference to, and the server
owes the client that inode until it is sent FUSE_FORGET. When a server
answers such a request with ENOENT it is describing a handle it was
obliged to honour, not a file that has gone away, and the client passes
that ENOENT to the caller unchanged: a file that never stopped existing
is reported missing.
This series reports that case as stale instead, so the VFS retries the
lookup rather than handing a spurious ENOENT to userspace. The path
opens say EOPENSTALE, which is what an open says when the cached
information it started from has gone stale and which path_openat()
resolves into ECHILD under LOOKUP_RCU or ESTALE otherwise; everything
else says ESTALE, which retry_estale() answers by repeating the lookup
once under LOOKUP_REVAL. NFS reports its own stale opens the same way.
The conversion is written twice, once in fs/fuse for any transport and
once in fs/fuse/virtio_fs.c at reply completion, and each one fixes the
symptom without the other. They are sent together because they were
found together; if only one is wanted, patch 3 is the general one and
patch 5 is the virtio-specific one, and either can be dropped.
Why it matters, and how it was found: a server that releases an inode
as soon as a rename displaces the name it was looked up by will refuse
opens for inodes the client still holds. On such a server, roughly one
open in eight during a rename race is refused while stat continues to
describe the file. That reaches real programs. git's t5318 fails 52 to
53 subtests per round against such a mount and 0 against local disk on
the same machine in the same alternating run, and the mechanism is
visible underneath it: .git/index opens ending ENOENT are 49 of 116 on
the affected mount against 9 of 157 on local disk, where those 9 are
the legitimate ones a fresh repository makes before an index exists.
The user-visible failure is a destroyed git index.
Measurements. All four arms are one kernel source, 6.18.5, with one
config, built from trees that differ only in whether these patches are
applied, run against the same server with the same probe for the same
duration:
arm fs/fuse virtio_fs opens refused
A stock stock 323076 45201 (14.0%)
B stock patched 54378 0
C patched stock 323776 0
D patched patched 315114 0
Every refusal on arm A is the contradiction: open said ENOENT while
stat on the same path still described the file. Arms C and A carry
almost identical open counts against the same server, so the pair
differing only in fs/fuse is 45201 refusals against none. Arms B and C
are why the two conversions are sent together but either can be
dropped: each removes the symptom without the other.
For the record, the same fault measured 12.9% on a 6.8 distro kernel
three weeks earlier, so nothing between 6.8 and 6.18 addressed it, which
is consistent with these patches applying to current mainline with only
hunk-offset movement.
The first two patches are selftest housekeeping that patch 4 needs: a
build artifact that was not ignored, and a rename of the libfuse3
pkg-config variables so a second test can share the same guard. Patch 4
is the test itself, which mounts a libfuse3 server that refuses one
request for a live nodeid and asserts the caller does not see ENOENT.
It skips where libfuse3 or fusermount3 is unavailable.
checkpatch is clean apart from two warnings I believe are false
positives, and I would rather name them than leave them to be
rediscovered: MAINTAINERS already carries
F: tools/testing/selftests/filesystems/fuse/ under FUSE FILESYSTEM
[CORE], so the new selftest needs no MAINTAINERS change; and the
char *fuse_argv[] in the test cannot be static const, because
struct fuse_args carries a plain char **argv that libfuse may modify.
Aaron Paterson (5):
selftests/fuse: ignore the built acl cache test
selftests/fuse: name the libfuse3 flags for the library
fuse: report a request refused for a live nodeid as stale
selftests/fuse: cover a request refused for a live nodeid
virtiofs: report a request refused for a live nodeid as stale
fs/fuse/dir.c | 10 +
fs/fuse/file.c | 11 +
fs/fuse/fuse_i.h | 19 +
fs/fuse/inode.c | 2 +-
fs/fuse/virtio_fs.c | 62 +++
.../selftests/filesystems/fuse/.gitignore | 2 +
.../selftests/filesystems/fuse/Makefile | 16 +-
.../filesystems/fuse/fuse_estale_test.c | 450 ++++++++++++++++++
8 files changed, 565 insertions(+), 7 deletions(-)
create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_estale_test.c
--
2.55.0.553.g4ad8c266be
next reply other threads:[~2026-08-27 23:37 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-27 23:37 Aaron Paterson [this message]
2026-08-27 23:37 ` [PATCH 1/5] selftests/fuse: ignore the built acl cache test Aaron Paterson
2026-08-27 23:37 ` [PATCH 2/5] selftests/fuse: name the libfuse3 flags for the library Aaron Paterson
2026-08-27 23:37 ` [PATCH 3/5] fuse: report a request refused for a live nodeid as stale Aaron Paterson
2026-08-27 23:38 ` [PATCH 4/5] selftests/fuse: cover a request refused for a live nodeid Aaron Paterson
2026-08-27 23:38 ` [PATCH 5/5] virtiofs: report a request refused for a live nodeid as stale Aaron Paterson
2026-08-28 2:02 ` [PATCH 0/5] fuse: report a request refused for a live nodeid Aaron Paterson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=cover.1787873791.git.apaterson@pm.me \
--to=apaterson@pm.me \
--cc=eperezma@redhat.com \
--cc=fuse-devel@lists.linux.dev \
--cc=gmaglione@redhat.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=miklos@szeredi.hu \
--cc=shuah@kernel.org \
--cc=stefanha@redhat.com \
--cc=vgoyal@redhat.com \
--cc=virtualization@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®