You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
mount: explore FUSE passthrough over the persistent on-disk cache (stock WSL2 kernel already has CONFIG_FUSE_PASSTHROUGH) #93
FUSE passthrough lets the kernel service read() and write() on a FUSE file directly against
a backing file descriptor, with the userspace daemon out of the data path entirely. Reads then
run at native filesystem speed with zero upcalls.
This is the one kernel-level mechanism that could actually lower the Direction B serving floor
past what userspace tuning reaches, and it needs no custom kernel and no out-of-tree module.
It is compiled into the stock WSL2 kernel already. Verified on this machine:
A real file to point at. Passthrough needs a backing descriptor on a local filesystem, so
this only pays off once cached file content is materialized on disk inside the VM. That makes
it a natural rider on the already-planned persistent on-disk cache follow-up rather than a
standalone change.
A newer libfuse. The FUSE_CAP_PASSTHROUGH / backing_id API is not in the installed
libfuse 3.14.0 (no matches in /usr/include/fuse3/); it landed in libfuse 3.17. So this means
bundling or requiring a newer libfuse, and keeping a non-passthrough fallback path.
Sketch
On open of a file whose content is fully present in the on-disk cache, register the cache file
as a backing id and return the passthrough handle. Reads bypass wsldrive completely.
On invalidation, drop the backing registration so the next open re-fetches.
Writes are the hard part: a passthrough write lands in the cache file without wsldrive seeing
it, so the write-through path needs a different mechanism (or passthrough stays read-only).
Status
Exploratory. Filed to record the finding and the verified kernel support. Should be scoped
only after the persistent on-disk cache exists, and after the cheap wins land, since those may
move the floor far enough that this is not worth the dependency on a bundled libfuse.
Context
Came out of an assessment of whether a custom WSL2 kernel module would speed up the boundary.
Conclusion was no: Direction A never touches the Linux kernel on its fast path, and virtiofs is
the in-kernel path yet measures ~20x slower here than wsldrive's userspace FUSE mount
(7686 ms vs 383 ms reading 3000 files). Passthrough is the exception worth keeping on the list
because it is a stock-kernel feature, not a module.
The sequencing note on #118 turned out to be wrong, in this issue's favour
I suggested on #118 that landing this first would make the inotify bridge substantially smaller, and
that the event path wanted the same mount infrastructure as the data path. Neither held up. #119
merged without touching passthrough, without a newer libfuse, and without a persistent on-disk cache:
the bridge raises events by replaying each far-side change as an ordinary syscall against the mount,
so the only thing it needs from the mount is that the mount exists.
Two consequences worth recording here.
This issue is not blocking anything. It was filed as exploratory and the reason to defer it —
that it should be scoped only after the persistent on-disk cache exists, and after the cheap wins
land — is unchanged. Nothing now waits on it.
One assumption in the sketch needs re-checking against what #119 established. The sketch has
reads bypassing wsldrive entirely once a file is registered as a backing id. The change-notification
path now depends on the mount seeing operations: op_getattr answers the bridge's own lookups, and op_create / op_unlink / op_rename recognise its pokes. Those are metadata operations, so
passthrough of read/write should not disturb them — but it is worth confirming rather than
assuming, because a passthrough handle that also serviced open would take the bridge's create poke
out of the daemon's view, and the failure mode there is silent (the poke gets forwarded to the agent
instead of recognised, which is how #119 briefly destroyed file content on the served side before CI
caught it).
Also relevant to the "is passthrough worth the dependency" question: the boundary is no longer the
only thing Direction B is competing on. Change notification is a capability virtiofs does not have,
and it costs nothing on the data path.
Both prerequisites are now satisfied on a stock distro — the libfuse blocker is gone
This issue records that FUSE_CAP_PASSTHROUGH / backing_id were absent from the installed libfuse
3.14.0, landed in 3.17, and would therefore mean "bundling or requiring a newer libfuse". That is no
longer true here. Ubuntu 26.04's stock libfuse3-dev:
libfuse: 3.18.2
kernel: 6.18.33.2-microsoft-standard-WSL2
/usr/include/fuse3/fuse_common.h:500: #define FUSE_CAP_PASSTHROUGH (1UL << 29)
/usr/include/fuse3/fuse_common.h:119: int32_t backing_id;
/usr/include/fuse3/fuse_lowlevel.h:1455: int fuse_passthrough_open(fuse_req_t req, int fd);
/usr/include/fuse3/fuse_lowlevel.h:1456: int fuse_passthrough_close(fuse_req_t req, int backing_id);
Kernel side unchanged from what this issue verified: CONFIG_FUSE_PASSTHROUGH=y. So requirement 2
costs nothing on a current distro, and only matters for the release binaries, which are built against
GCC 12 / Ubuntu 22.04 for compatibility — that is where the version floor would still bite, not on
the development machine.
Three things the headers add to the sketch:
fuse_passthrough_open is offered on open, opendirandcreate, and returns a backing id
to put in fi->backing_id. Requirement 1 in this issue — that it needs a real file to point at —
is confirmed by the API shape rather than inferred.
One backing id per node, per the doc comment ("Currently there should be only one backing id
per node / backing file"), which constrains how the cache would have to key registrations.
Stacking depth is capped at 1 backing file, because the kernel's FILESYSTEM_MAX_STACK_DEPTH of 2 counts the passthrough layer itself. A cache file on an ordinary
ext4 is fine; a cache file that itself lived on another stacked filesystem would not be.
Unrelated but worth recording while I was in there
The same kernel now reports CONFIG_FUSE_IO_URING=y, which was not in this issue's original
capability dump. That is a different lever on the same floor — it cuts per-request overhead for the
whole FUSE path rather than bypassing it for reads only — and unlike passthrough it needs no backing
file, so it does not depend on the persistent on-disk cache existing first. Might be the cheaper
experiment of the two. Not filing it separately unless you want it as its own issue.
Status unchanged
Still exploratory, still correctly deferred behind the persistent on-disk cache. This only removes
one of the two stated obstacles and sharpens the sketch; nothing here argues for scoping it now.
Activity
zcsizmadia commentedon Sep 13, 2026
The sequencing note on #118 turned out to be wrong, in this issue's favour
I suggested on #118 that landing this first would make the inotify bridge substantially smaller, and
that the event path wanted the same mount infrastructure as the data path. Neither held up. #119
merged without touching passthrough, without a newer libfuse, and without a persistent on-disk cache:
the bridge raises events by replaying each far-side change as an ordinary syscall against the mount,
so the only thing it needs from the mount is that the mount exists.
Two consequences worth recording here.
This issue is not blocking anything. It was filed as exploratory and the reason to defer it —
that it should be scoped only after the persistent on-disk cache exists, and after the cheap wins
land — is unchanged. Nothing now waits on it.
One assumption in the sketch needs re-checking against what #119 established. The sketch has
reads bypassing wsldrive entirely once a file is registered as a backing id. The change-notification
path now depends on the mount seeing operations:
op_getattranswers the bridge's own lookups, andop_create/op_unlink/op_renamerecognise its pokes. Those are metadata operations, sopassthrough of
read/writeshould not disturb them — but it is worth confirming rather thanassuming, because a passthrough handle that also serviced
openwould take the bridge's create pokeout of the daemon's view, and the failure mode there is silent (the poke gets forwarded to the agent
instead of recognised, which is how #119 briefly destroyed file content on the served side before CI
caught it).
Also relevant to the "is passthrough worth the dependency" question: the boundary is no longer the
only thing Direction B is competing on. Change notification is a capability virtiofs does not have,
and it costs nothing on the data path.
🤖 Generated with Claude Code
zcsizmadia commentedon Sep 13, 2026
Both prerequisites are now satisfied on a stock distro — the libfuse blocker is gone
This issue records that
FUSE_CAP_PASSTHROUGH/backing_idwere absent from the installed libfuse3.14.0, landed in 3.17, and would therefore mean "bundling or requiring a newer libfuse". That is no
longer true here. Ubuntu 26.04's stock
libfuse3-dev:Kernel side unchanged from what this issue verified:
CONFIG_FUSE_PASSTHROUGH=y. So requirement 2costs nothing on a current distro, and only matters for the release binaries, which are built against
GCC 12 / Ubuntu 22.04 for compatibility — that is where the version floor would still bite, not on
the development machine.
Three things the headers add to the sketch:
fuse_passthrough_openis offered onopen,opendirandcreate, and returns a backing idto put in
fi->backing_id. Requirement 1 in this issue — that it needs a real file to point at —is confirmed by the API shape rather than inferred.
per node / backing file"), which constrains how the cache would have to key registrations.
FILESYSTEM_MAX_STACK_DEPTHof 2 counts the passthrough layer itself. A cache file on an ordinaryext4 is fine; a cache file that itself lived on another stacked filesystem would not be.
Unrelated but worth recording while I was in there
The same kernel now reports
CONFIG_FUSE_IO_URING=y, which was not in this issue's originalcapability dump. That is a different lever on the same floor — it cuts per-request overhead for the
whole FUSE path rather than bypassing it for reads only — and unlike passthrough it needs no backing
file, so it does not depend on the persistent on-disk cache existing first. Might be the cheaper
experiment of the two. Not filing it separately unless you want it as its own issue.
Status unchanged
Still exploratory, still correctly deferred behind the persistent on-disk cache. This only removes
one of the two stated obstacles and sharpens the sketch; nothing here argues for scoping it now.
🤖 Generated with Claude Code