lib/fs: Cache user lookups (#8496)

This commit is contained in:
Jakob Borg 2022-08-12 07:48:00 +02:00 committed by GitHub
parent 06273875ae
commit eb81f7400c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 85 additions and 16 deletions

View File

@ -10,6 +10,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"os/user"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@ -46,8 +47,13 @@ type BasicFilesystem struct {
root string root string
junctionsAsDirs bool junctionsAsDirs bool
options []Option options []Option
userCache *userCache
groupCache *groupCache
} }
type userCache = valueCache[string, *user.User]
type groupCache = valueCache[string, *user.Group]
func newBasicFilesystem(root string, opts ...Option) *BasicFilesystem { func newBasicFilesystem(root string, opts ...Option) *BasicFilesystem {
if root == "" { if root == "" {
root = "." // Otherwise "" becomes "/" below root = "." // Otherwise "" becomes "/" below
@ -86,6 +92,8 @@ func newBasicFilesystem(root string, opts ...Option) *BasicFilesystem {
fs := &BasicFilesystem{ fs := &BasicFilesystem{
root: root, root: root,
options: opts, options: opts,
userCache: newValueCache(time.Hour, user.LookupId),
groupCache: newValueCache(time.Hour, user.LookupGroupId),
} }
for _, opt := range opts { for _, opt := range opts {
opt.apply(fs) opt.apply(fs)

View File

@ -14,5 +14,5 @@ import (
) )
func (f *BasicFilesystem) PlatformData(name string) (protocol.PlatformData, error) { func (f *BasicFilesystem) PlatformData(name string) (protocol.PlatformData, error) {
return unixPlatformData(f, name) return unixPlatformData(f, name, f.userCache, f.groupCache)
} }

View File

@ -8,7 +8,6 @@ package fs
import ( import (
"fmt" "fmt"
"os/user"
"github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/protocol"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
@ -35,12 +34,10 @@ func (f *BasicFilesystem) PlatformData(name string) (protocol.PlatformData, erro
return protocol.PlatformData{}, fmt.Errorf("get owner for %s: %w", rootedName, err) return protocol.PlatformData{}, fmt.Errorf("get owner for %s: %w", rootedName, err)
} }
// The owner SID might represent a user or a group. We try to look it up
// as both, and set the appropriate fields in the OS data.
pd := &protocol.WindowsData{} pd := &protocol.WindowsData{}
if us, err := user.LookupId(owner.String()); err == nil { if us := f.userCache.lookup(owner.String()); us != nil {
pd.OwnerName = us.Username pd.OwnerName = us.Username
} else if gr, err := user.LookupGroupId(owner.String()); err == nil { } else if gr := f.groupCache.lookup(owner.String()); gr != nil {
pd.OwnerName = gr.Name pd.OwnerName = gr.Name
pd.OwnerIsGroup = true pd.OwnerIsGroup = true
} else { } else {

View File

@ -16,7 +16,7 @@ import (
"strings" "strings"
) )
func (BasicFilesystem) SymlinksSupported() bool { func (*BasicFilesystem) SymlinksSupported() bool {
return true return true
} }

View File

@ -15,6 +15,7 @@ import (
"math/rand" "math/rand"
"net/url" "net/url"
"os" "os"
"os/user"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -61,6 +62,8 @@ type fakeFS struct {
insens bool insens bool
withContent bool withContent bool
latency time.Duration latency time.Duration
userCache *userCache
groupCache *groupCache
} }
type fakeFSCounters struct { type fakeFSCounters struct {
@ -109,6 +112,8 @@ func newFakeFilesystem(rootURI string, _ ...Option) *fakeFS {
mtime: time.Now(), mtime: time.Now(),
children: make(map[string]*fakeEntry), children: make(map[string]*fakeEntry),
}, },
userCache: newValueCache(time.Hour, user.LookupId),
groupCache: newValueCache(time.Hour, user.LookupGroupId),
} }
files, _ := strconv.Atoi(params.Get("files")) files, _ := strconv.Atoi(params.Get("files"))
@ -658,7 +663,7 @@ func (fs *fakeFS) SameFile(fi1, fi2 FileInfo) bool {
} }
func (fs *fakeFS) PlatformData(name string) (protocol.PlatformData, error) { func (fs *fakeFS) PlatformData(name string) (protocol.PlatformData, error) {
return unixPlatformData(fs, name) return unixPlatformData(fs, name, fs.userCache, fs.groupCache)
} }
func (*fakeFS) underlying() (Filesystem, bool) { func (*fakeFS) underlying() (Filesystem, bool) {

View File

@ -7,8 +7,9 @@
package fs package fs
import ( import (
"os/user"
"strconv" "strconv"
"sync"
"time"
"github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/protocol"
) )
@ -16,7 +17,7 @@ import (
// unixPlatformData is used on all platforms, because apart from being the // unixPlatformData is used on all platforms, because apart from being the
// implementation for BasicFilesystem on Unixes it's also the implementation // implementation for BasicFilesystem on Unixes it's also the implementation
// in fakeFS. // in fakeFS.
func unixPlatformData(fs Filesystem, name string) (protocol.PlatformData, error) { func unixPlatformData(fs Filesystem, name string, userCache *userCache, groupCache *groupCache) (protocol.PlatformData, error) {
stat, err := fs.Lstat(name) stat, err := fs.Lstat(name)
if err != nil { if err != nil {
return protocol.PlatformData{}, err return protocol.PlatformData{}, err
@ -24,8 +25,8 @@ func unixPlatformData(fs Filesystem, name string) (protocol.PlatformData, error)
ownerUID := stat.Owner() ownerUID := stat.Owner()
ownerName := "" ownerName := ""
if u, err := user.LookupId(strconv.Itoa(ownerUID)); err == nil && u.Username != "" { if user := userCache.lookup(strconv.Itoa(ownerUID)); user != nil {
ownerName = u.Username ownerName = user.Username
} else if ownerUID == 0 { } else if ownerUID == 0 {
// We couldn't look up a name, but UID zero should be "root". This // We couldn't look up a name, but UID zero should be "root". This
// fixup works around the (unlikely) situation where the ownership // fixup works around the (unlikely) situation where the ownership
@ -38,8 +39,8 @@ func unixPlatformData(fs Filesystem, name string) (protocol.PlatformData, error)
groupID := stat.Group() groupID := stat.Group()
groupName := "" groupName := ""
if g, err := user.LookupGroupId(strconv.Itoa(groupID)); err == nil && g.Name != "" { if group := groupCache.lookup(strconv.Itoa(ownerUID)); group != nil {
groupName = g.Name groupName = group.Name
} else if groupID == 0 { } else if groupID == 0 {
groupName = "root" groupName = "root"
} }
@ -53,3 +54,39 @@ func unixPlatformData(fs Filesystem, name string) (protocol.PlatformData, error)
}, },
}, nil }, nil
} }
type valueCache[K comparable, V any] struct {
validity time.Duration
fill func(K) (V, error)
mut sync.Mutex
cache map[K]cacheEntry[V]
}
type cacheEntry[V any] struct {
value V
when time.Time
}
func newValueCache[K comparable, V any](validity time.Duration, fill func(K) (V, error)) *valueCache[K, V] {
return &valueCache[K, V]{
validity: validity,
fill: fill,
cache: make(map[K]cacheEntry[V]),
}
}
func (c *valueCache[K, V]) lookup(key K) V {
c.mut.Lock()
defer c.mut.Unlock()
if e, ok := c.cache[key]; ok && time.Since(e.when) < c.validity {
return e.value
}
var e cacheEntry[V]
if val, err := c.fill(key); err == nil {
e.value = val
}
e.when = time.Now()
c.cache[key] = e
return e.value
}

View File

@ -1028,3 +1028,25 @@ func testConfig() (Config, context.CancelFunc) {
EventLogger: evLogger, EventLogger: evLogger,
}, cancel }, cancel
} }
func BenchmarkWalk(b *testing.B) {
testFs := fs.NewFilesystem(fs.FilesystemTypeBasic, b.TempDir())
for i := 0; i < 100; i++ {
if err := testFs.Mkdir(fmt.Sprintf("dir%d", i), 0755); err != nil {
b.Fatal(err)
}
for j := 0; j < 100; j++ {
if fd, err := testFs.Create(fmt.Sprintf("dir%d/file%d", i, j)); err != nil {
b.Fatal(err)
} else {
fd.Close()
}
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
walkDir(testFs, "/", nil, nil, 0)
}
}