mirror of
https://github.com/octoleo/syncthing.git
synced 2025-01-03 15:17:25 +00:00
lib/fs: Cache user lookups (#8496)
This commit is contained in:
parent
06273875ae
commit
eb81f7400c
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -16,7 +16,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (BasicFilesystem) SymlinksSupported() bool {
|
func (*BasicFilesystem) SymlinksSupported() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user