From 9f15e3935c9d3a6a1a655947037a1363854967a9 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Tue, 18 Aug 2026 20:55:17 -0700 Subject: [PATCH] mount: reuse the listed entry's path instead of rebuilding it (#10817) readdir built dirPath.Child(name) for every child while entry.FullPath was already that exact string, from NewFullPath in the meta cache store or from FromPbEntry on the read-through path. One allocation per entry, and on a wide tree with long paths that is most of what a listing allocates. BenchmarkReadDirectory/kernel_readdirplus over 200k entries: 2,039,656 -> 1,839,318 allocs/op, 174.5 -> 167.8 MB/op, 152.6 -> 135.1 ms/op. --- weed/mount/weedfs_dir_read.go | 7 ++++++- weed/mount/weedfs_dir_read_pagination_test.go | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/weed/mount/weedfs_dir_read.go b/weed/mount/weedfs_dir_read.go index e5493001b..57186e08c 100644 --- a/weed/mount/weedfs_dir_read.go +++ b/weed/mount/weedfs_dir_read.go @@ -3,6 +3,7 @@ package mount import ( "context" "errors" + "strings" "sync" "time" @@ -219,7 +220,11 @@ func (wfs *WFS) doReadDirectory(input *fuse.ReadIn, out DirEntrySink, isPlusMode processEachEntryFn := func(entry *filer.Entry, index int64) bool { dirEntry.Name = entry.Name() dirEntry.Mode = toSyscallMode(entry.Mode) - childPath := dirPath.Child(dirEntry.Name) + // Rebuild only for a sanitized name: that is the one a LOOKUP carries. + childPath := entry.FullPath + if !strings.HasSuffix(string(childPath), dirEntry.Name) { + childPath = dirPath.Child(dirEntry.Name) + } var inode uint64 if takesLookupRef { inode = wfs.inodeToPath.Lookup(childPath, entry.Crtime.Unix(), entry.IsDirectory(), len(entry.HardLinkId) > 0, entry.Inode, false) diff --git a/weed/mount/weedfs_dir_read_pagination_test.go b/weed/mount/weedfs_dir_read_pagination_test.go index c2690b403..ba38b3db1 100644 --- a/weed/mount/weedfs_dir_read_pagination_test.go +++ b/weed/mount/weedfs_dir_read_pagination_test.go @@ -336,3 +336,24 @@ func TestReadDirDirectTrimsConsumedEntries(t *testing.T) { t.Errorf("handle held %d entries at peak, want well under the %d in the directory", peak, total) } } + +// The table has to key on the sanitized name, not the stored bytes. +func TestReadDirPlusSanitizedName(t *testing.T) { + dir := util.FullPath("/images") + const rawName = "bad\xffname.jpg" + wfs := newPagingWFS(t, dir, []string{"good.jpg", rawName}, 0) + dirInode, _ := wfs.inodeToPath.GetInode(dir) + + sink := &benchSink{plus: true, takesRef: true, sinkLimit: 16} + if got := walkOnce(t, wfs, dirInode, sink, false); got != 4 { + t.Fatalf("listed %d entries, want 4 (. .. and two children)", got) + } + + sanitized := dir.Child(util.SanitizeUTF8Name(rawName)) + if !wfs.inodeToPath.HasPath(sanitized) { + t.Errorf("%s not in the inode table", sanitized) + } + if wfs.inodeToPath.HasPath(dir.Child(rawName)) { + t.Errorf("%s keyed on the unsanitized name", dir.Child(rawName)) + } +}