mount: auto-enable Linux FUSE passthrough for read-only files

On a read-only open, when the kernel supports FUSE_PASSTHROUGH (Linux >= 6.9, privileged mount), materialize the file into a local backing file and register it via RegisterBackingFd so the kernel serves reads and mmap directly from the backing file, bypassing the mount process.

No opt-in flag: support is probed by the first RegisterBackingFd call and latched off on failure, so unsupported mounts transparently use the normal read path. Gated to read-only opens of files up to -fusePassthroughMaxMB (default 256; 0 disables). The backing is shared per inode and torn down when the last open is released; MaxStackDepth is set so the kernel accepts registration.

Tradeoff: a passthrough handle reads a point-in-time snapshot, so concurrent writes are not reflected while the file stays open - best for immutable / read-mostly data. The data path is unchanged when disabled or unsupported, and the feature is a no-op on non-Linux platforms.
This commit is contained in:
Chris Lu
2026-06-20 16:05:54 -07:00
parent 395d2c80af
commit c275e186c8
7 changed files with 209 additions and 3 deletions
+2
View File
@@ -74,6 +74,7 @@ type MountOptions struct {
cacheSymlink *bool
fuseMaxBackground *int
fuseCongestionThreshold *int
fusePassthroughMaxMB *int64
// macOS-specific FUSE options
novncache *bool
@@ -162,6 +163,7 @@ func init() {
mountOptions.writebackCache = cmdMount.Flag.Bool("writebackCache", false, "enable FUSE writeback cache for improved write performance (at risk of data loss on crash)")
mountOptions.asyncDio = cmdMount.Flag.Bool("asyncDio", false, "enable async direct I/O for better concurrency")
mountOptions.cacheSymlink = cmdMount.Flag.Bool("cacheSymlink", false, "enable symlink caching to reduce metadata lookups")
mountOptions.fusePassthroughMaxMB = cmdMount.Flag.Int64("fusePassthroughMaxMB", 256, "(Linux >=6.9) auto-enable FUSE passthrough for read-only files up to this size in MB: the kernel serves reads/mmap directly, bypassing the mount process. Auto-detected; falls back when unsupported (needs a privileged mount). 0 disables. A passthrough handle reads a point-in-time snapshot.")
mountOptions.fuseMaxBackground = cmdMount.Flag.Int("fuse.maxBackground", 128, "FUSE max_background: maximum in-flight asynchronous requests the kernel will queue. Heavy upload workloads may benefit from higher values (e.g. 2048). Equivalent to writing /sys/fs/fuse/connections/<id>/max_background. If -fuse.congestionThreshold is 0, the kernel derives it as 3/4 of this value.")
mountOptions.fuseCongestionThreshold = cmdMount.Flag.Int("fuse.congestionThreshold", 0, "FUSE congestion_threshold: in-flight async request count at which the kernel marks the FUSE bdi as congested and throttles new submissions. 0 means use the default (3/4 of -fuse.maxBackground). Equivalent to writing /sys/fs/fuse/connections/<id>/congestion_threshold. The kernel silently clamps this to -fuse.maxBackground when set higher.")
+11
View File
@@ -315,6 +315,11 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
if option.cacheSymlink != nil && *option.cacheSymlink {
fuseMountOptions.EnableSymlinkCaching = true
}
if option.fusePassthroughMaxMB != nil && *option.fusePassthroughMaxMB > 0 {
// FUSE_PASSTHROUGH requires a non-zero stacking depth advertised in
// FUSE_INIT; without it the kernel rejects backing-file registration.
fuseMountOptions.MaxStackDepth = 1
}
// find mount point
mountRoot := filerMountRootPath
@@ -328,6 +333,11 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
cacheDirForWrite = cacheDirForRead
}
passthroughMaxMB := int64(0)
if option.fusePassthroughMaxMB != nil {
passthroughMaxMB = *option.fusePassthroughMaxMB
}
seaweedFileSystem := mount.NewSeaweedFileSystem(&mount.Option{
MountDirectory: dir,
FilerAddresses: filerAddresses,
@@ -373,6 +383,7 @@ func RunMount(option *MountOptions, umask os.FileMode) bool {
EnableDistributedLock: option.distributedLock != nil && *option.distributedLock,
WritebackCache: option.writebackCache != nil && *option.writebackCache,
PosixDirNlink: option.posixDirNlink != nil && *option.posixDirNlink,
FusePassthroughMaxMB: passthroughMaxMB,
// Peer chunk sharing
PeerEnabled: option.peerEnabled != nil && *option.peerEnabled,
PeerListen: peerStringOrEmpty(option.peerListen),
+11
View File
@@ -51,6 +51,16 @@ type FileHandle struct {
// for debugging
mirrorFile *os.File
// FUSE_PASSTHROUGH (Linux >= 6.9) state. On a read-only open the whole
// file is materialized into passthroughFile and registered with the
// kernel so reads/mmap bypass this daemon. There is one handle per inode,
// so this state is shared by all concurrent opens and torn down in
// ReleaseHandle when the last open closes. Guarded by passthroughMu.
passthroughMu sync.Mutex
passthroughTried bool
passthroughBackingID int32
passthroughFile *os.File
}
func newFileHandle(wfs *WFS, handleId FileHandleId, inode uint64, entry *filer_pb.Entry) *FileHandle {
@@ -155,6 +165,7 @@ func (fh *FileHandle) ReleaseHandle() {
defer fh.wfs.fhLockTable.ReleaseLock(fh.fh, fhActiveLock)
fh.dirtyPages.Destroy()
fh.teardownPassthrough()
if IsDebugFileReadWrite {
fh.mirrorFile.Close()
}
+14
View File
@@ -114,6 +114,16 @@ type Option struct {
// When false (default), directories report nlink=2.
PosixDirNlink bool
// FusePassthroughMaxMB auto-enables Linux FUSE_PASSTHROUGH for read-only
// opens of files up to this size (MB). When > 0 (the default) and the
// kernel supports it (>= 6.9, privileged mount), the whole file is
// materialized into a local backing file and the kernel serves reads/mmap
// directly from it, bypassing this mount process. Support is auto-detected
// and silently falls back when unavailable; set to 0 to disable. NOTE: a
// passthrough handle reads a point-in-time snapshot — concurrent writes are
// not reflected while the file stays open.
FusePassthroughMaxMB int64
uniqueCacheDirForRead string
uniqueCacheDirForWrite string
}
@@ -138,6 +148,10 @@ type WFS struct {
dhMap *DirectoryHandleToInode
fuseServer *fuse.Server
IsOverQuota bool
// passthroughDisabled latches true after the first RegisterBackingFd
// failure (unprivileged mount, or a kernel without passthrough), so we
// stop attempting the ioctl on every subsequent open.
passthroughDisabled atomic.Bool
fhLockTable *util.LockTable[FileHandleId]
hardLinkLockTable *util.LockTable[string]
posixLocks *PosixLockTable
+8 -3
View File
@@ -68,10 +68,15 @@ func (wfs *WFS) Open(cancel <-chan struct{}, in *fuse.OpenIn, out *fuse.OpenOut)
out.Fh = uint64(fileHandle.fh)
out.OpenFlags = 0
// For read-only opens, set FOPEN_KEEP_CACHE when the file's mtime
// has not changed since the last open. This tells the kernel to
// preserve its existing page cache, avoiding redundant reads.
// For read-only opens, first try FUSE passthrough (auto-enabled where
// the kernel supports it): the kernel serves reads/mmap directly from a
// local backing file, bypassing this daemon. If passthrough is not
// enabled, fall back to FOPEN_KEEP_CACHE when the file's mtime has not
// changed since the last open, preserving the kernel page cache.
if in.Flags&fuse.O_ANYWRITE == 0 {
if wfs.tryEnablePassthrough(fileHandle, out) {
return status
}
if entry := fileHandle.GetEntry(); entry != nil && entry.Attributes != nil {
wfs.applyKeepCacheFlag(in.NodeId, entry, out)
}
+149
View File
@@ -0,0 +1,149 @@
//go:build linux
package mount
import (
"fmt"
"os"
"github.com/seaweedfs/go-fuse/v2/fuse"
"github.com/seaweedfs/seaweedfs/weed/glog"
)
// tryEnablePassthrough materializes a read-only file into a local backing file
// and registers it with the kernel via FUSE_PASSTHROUGH, so reads and mmap on
// this open are served by the kernel directly from the backing file without
// round-tripping through this daemon.
//
// It is auto-enabled — there is no opt-in flag. Kernel support (Linux >= 6.9 and
// a privileged mount) is probed by the first RegisterBackingFd call and latched
// off on failure, so unsupported mounts transparently use the normal read path.
// It is gated to read-only opens of files no larger than FusePassthroughMaxMB
// (set that to 0 to disable entirely).
//
// Tradeoff: the backing file is a point-in-time snapshot taken at open. While an
// inode stays open in passthrough, concurrent writes (this mount or another) are
// not reflected — best for immutable / read-mostly data.
func (wfs *WFS) tryEnablePassthrough(fh *FileHandle, out *fuse.OpenOut) bool {
capBytes := wfs.option.FusePassthroughMaxMB * 1024 * 1024
if capBytes <= 0 || wfs.fuseServer == nil {
return false
}
if wfs.passthroughDisabled.Load() {
return false
}
entry := fh.GetEntry()
if entry == nil || entry.Attributes == nil {
return false
}
fileSize := int64(entry.Attributes.FileSize)
if fileSize <= 0 || fileSize > capBytes {
return false
}
fh.passthroughMu.Lock()
defer fh.passthroughMu.Unlock()
// Establish the backing file once per handle (one handle per inode), then
// reuse it for every concurrent open of the same inode.
if !fh.passthroughTried {
fh.passthroughTried = true
backingID, file, err := wfs.setupPassthroughBacking(fh, fileSize)
if err != nil {
glog.V(1).Infof("passthrough setup %s: %v", fh.FullPath(), err)
return false
}
fh.passthroughBackingID = backingID
fh.passthroughFile = file
glog.V(2).Infof("passthrough enabled for %s (%d bytes, backingID %d)", fh.FullPath(), fileSize, backingID)
}
if fh.passthroughBackingID == 0 {
return false
}
out.BackingID = fh.passthroughBackingID
out.OpenFlags |= fuse.FOPEN_PASSTHROUGH
// Passthrough and the page-cache keep-cache hint are mutually exclusive;
// the backing file owns caching once passthrough is on.
out.OpenFlags &^= fuse.FOPEN_KEEP_CACHE
return true
}
// setupPassthroughBacking downloads the whole file into a temp file and
// registers its fd with the kernel, returning the backing ID.
func (wfs *WFS) setupPassthroughBacking(fh *FileHandle, fileSize int64) (int32, *os.File, error) {
dir := wfs.option.CacheDirForRead
if dir == "" {
dir = os.TempDir()
}
if err := os.MkdirAll(dir, 0o755); err != nil {
return 0, nil, err
}
file, err := os.CreateTemp(dir, "swpassthrough-")
if err != nil {
return 0, nil, err
}
// Unlink immediately: the kernel keeps the inode alive via the registered
// fd, so no temp file is left behind on close or crash.
os.Remove(file.Name())
if err := materializeFile(fh, file, fileSize); err != nil {
file.Close()
return 0, nil, err
}
backingID, errno := wfs.fuseServer.RegisterBackingFd(&fuse.BackingMap{
Fd: int32(file.Fd()),
})
if errno != 0 {
// Typically EPERM (unprivileged mount) or ENOTTY (kernel <6.9 or
// MaxStackDepth unset). Latch off so we stop attempting per open.
wfs.passthroughDisabled.Store(true)
file.Close()
return 0, nil, fmt.Errorf("RegisterBackingFd: %v", errno)
}
return backingID, file, nil
}
// materializeFile copies the file's full contents into dst using the same read
// path the kernel Read handler uses, so the backing bytes are identical to what
// a normal read would return.
func materializeFile(fh *FileHandle, dst *os.File, fileSize int64) error {
buf := make([]byte, 1024*1024)
for offset := int64(0); offset < fileSize; {
// readDataByFileHandle reads chunks + dirty pages and maps EOF to nil.
n, err := readDataByFileHandle(buf, fh, offset)
if n > 0 {
if _, werr := dst.WriteAt(buf[:int(n)], offset); werr != nil {
return werr
}
offset += n
}
if err != nil {
return err
}
if n == 0 {
break
}
}
return dst.Sync()
}
// teardownPassthrough unregisters and closes the backing file. Called from
// ReleaseHandle when the last open of the inode is closed.
func (fh *FileHandle) teardownPassthrough() {
fh.passthroughMu.Lock()
defer fh.passthroughMu.Unlock()
if fh.passthroughBackingID != 0 {
if errno := fh.wfs.fuseServer.UnregisterBackingFd(fh.passthroughBackingID); errno != 0 {
glog.Warningf("UnregisterBackingFd inode %d: %v", fh.inode, errno)
}
fh.passthroughBackingID = 0
}
if fh.passthroughFile != nil {
fh.passthroughFile.Close()
fh.passthroughFile = nil
}
}
+14
View File
@@ -0,0 +1,14 @@
//go:build !linux
package mount
import "github.com/seaweedfs/go-fuse/v2/fuse"
// FUSE_PASSTHROUGH is a Linux-only kernel feature (>= 6.9). On other platforms
// these are no-ops so the mount package builds and behaves as before.
func (wfs *WFS) tryEnablePassthrough(fh *FileHandle, out *fuse.OpenOut) bool {
return false
}
func (fh *FileHandle) teardownPassthrough() {}