diff --git a/weed/mount/weedfs.go b/weed/mount/weedfs.go index 032eed7cc..4cb68706e 100644 --- a/weed/mount/weedfs.go +++ b/weed/mount/weedfs.go @@ -802,9 +802,33 @@ func (wfs *WFS) onEntryInvalidation(invalidation meta_cache.EntryInvalidation) { if listener != nil { listener(invalidation) } + wfs.invalidateKernelDirListing(invalidation.Path) wfs.invalidateOpenFileHandle(invalidation) } +// invalidateKernelDirListing drops the kernel's cached listing of the directory +// holding path. Safe here because invalidations run on their own worker, never +// on a thread serving a kernel request; notifying from a handler can deadlock +// against the page it holds, which is why the file paths avoid InodeNotify. +// A directory the kernel has not looked up has no inode here and nothing +// cached, so it is skipped. +func (wfs *WFS) invalidateKernelDirListing(path util.FullPath) { + server := wfs.fuseServer + if server == nil { + return + } + dir, _ := path.DirAndName() + dirInode, found := wfs.inodeToPath.GetInode(util.FullPath(dir)) + if !found { + return + } + // ENOENT is the kernel not holding the inode, ENOSYS a kernel without the + // notify; neither is worth a line. + if status := server.InodeNotify(dirInode, 0, -1); status != fuse.OK && status != fuse.ENOENT && status != fuse.ENOSYS { + glog.V(4).Infof("invalidate kernel listing of %s: %v", dir, status) + } +} + // MountRoot is the filer path this mount is rooted at. Event paths are absolute // on the filer; a front end that addresses files relative to the mount needs it // to translate them. diff --git a/weed/mount/weedfs_dir_read.go b/weed/mount/weedfs_dir_read.go index faeb01857..428d25ebc 100644 --- a/weed/mount/weedfs_dir_read.go +++ b/weed/mount/weedfs_dir_read.go @@ -109,6 +109,12 @@ func (wfs *WFS) OpenDir(cancel <-chan struct{}, input *fuse.OpenIn, out *fuse.Op } dhid, _ := wfs.AcquireDirectoryHandle() out.Fh = uint64(dhid) + // Let the kernel keep the listing in the directory's page cache, so + // reopening the directory does not reach the mount at all. Local mutations + // drop that cache in the kernel; remote ones arrive through the metadata + // subscription, which notifies the kernel per changed directory. A kernel + // too old for the flag ignores it. + out.OpenFlags |= fuse.FOPEN_CACHE_DIR | fuse.FOPEN_KEEP_CACHE return fuse.OK } diff --git a/weed/mount/weedfs_dir_read_cache_flags_test.go b/weed/mount/weedfs_dir_read_cache_flags_test.go new file mode 100644 index 000000000..0ff8273b7 --- /dev/null +++ b/weed/mount/weedfs_dir_read_cache_flags_test.go @@ -0,0 +1,32 @@ +package mount + +import ( + "testing" + + "github.com/seaweedfs/go-fuse/v2/fuse" + "github.com/seaweedfs/seaweedfs/weed/util" +) + +// TestOpenDirEnablesKernelListingCache pins the reply flags: without them the +// kernel calls back for every enumeration, and losing them would silently +// revert repeat listings to full walks of the mount. +func TestOpenDirEnablesKernelListingCache(t *testing.T) { + dir := util.FullPath("/images") + wfs := newBenchWFS(t, dir, 4) + dirInode, _ := wfs.inodeToPath.GetInode(dir) + + var out fuse.OpenOut + if status := wfs.OpenDir(nil, &fuse.OpenIn{InHeader: fuse.InHeader{NodeId: dirInode}}, &out); status != fuse.OK { + t.Fatalf("OpenDir: %v", status) + } + defer wfs.ReleaseDir(&fuse.ReleaseIn{Fh: out.Fh}) + + for _, want := range []struct { + name string + flag uint32 + }{{"FOPEN_CACHE_DIR", fuse.FOPEN_CACHE_DIR}, {"FOPEN_KEEP_CACHE", fuse.FOPEN_KEEP_CACHE}} { + if out.OpenFlags&want.flag == 0 { + t.Errorf("OpenDir reply lacks %s", want.name) + } + } +}