diff --git a/lib/fs/casefs.go b/lib/fs/casefs.go index ef98c79e1..aebf4b9fd 100644 --- a/lib/fs/casefs.go +++ b/lib/fs/casefs.go @@ -386,7 +386,6 @@ func (f *caseFilesystem) checkCaseExisting(name string) error { } type defaultRealCaser struct { - fs Filesystem cache caseCache } @@ -396,13 +395,12 @@ func newDefaultRealCaser(fs Filesystem) *defaultRealCaser { if err != nil { panic(err) } - caser := &defaultRealCaser{ - fs: fs, + return &defaultRealCaser{ cache: caseCache{ + fs: fs, TwoQueueCache: cache, }, } - return caser } func (r *defaultRealCaser) realCase(name string) (string, error) { @@ -414,27 +412,6 @@ func (r *defaultRealCaser) realCase(name string) (string, error) { for _, comp := range PathComponents(name) { node := r.cache.getExpireAdd(realName) - node.once.Do(func() { - dirNames, err := r.fs.DirNames(realName) - if err != nil { - r.cache.Remove(realName) - node.err = err - return - } - - num := len(dirNames) - node.children = make(map[string]struct{}, num) - node.lowerToReal = make(map[string]string, num) - lastLower := "" - for _, n := range dirNames { - node.children[n] = struct{}{} - lower := UnicodeLowercaseNormalized(n) - if lower != lastLower { - node.lowerToReal[lower] = n - lastLower = n - } - } - }) if node.err != nil { return "", node.err } @@ -457,10 +434,29 @@ func (r *defaultRealCaser) dropCache() { r.cache.Purge() } -func newCaseNode() *caseNode { - return &caseNode{ - expires: time.Now().Add(caseCacheTimeout), +type caseCache struct { + *lru.TwoQueueCache + fs Filesystem + mut sync.Mutex +} + +// getExpireAdd gets an entry for the given key. If no entry exists, or it is +// expired a new one is created and added to the cache. +func (c *caseCache) getExpireAdd(key string) *caseNode { + c.mut.Lock() + defer c.mut.Unlock() + v, ok := c.Get(key) + if !ok { + node := newCaseNode(key, c.fs) + c.Add(key, node) + return node } + node := v.(*caseNode) + if node.expires.Before(time.Now()) { + node = newCaseNode(key, c.fs) + c.Add(key, node) + } + return node } // The keys to children are "real", case resolved names of the path @@ -474,30 +470,32 @@ type caseNode struct { expires time.Time lowerToReal map[string]string children map[string]struct{} - once sync.Once err error } -type caseCache struct { - *lru.TwoQueueCache - mut sync.Mutex -} - -// getExpireAdd gets an entry for the given key. If no entry exists, or it is -// expired a new one is created and added to the cache. -func (c *caseCache) getExpireAdd(key string) *caseNode { - c.mut.Lock() - defer c.mut.Unlock() - v, ok := c.Get(key) - if !ok { - node := newCaseNode() - c.Add(key, node) +func newCaseNode(name string, filesystem Filesystem) *caseNode { + node := new(caseNode) + dirNames, err := filesystem.DirNames(name) + // Set expiry after calling DirNames in case this is super-slow + // (e.g. dirs with many children on android) + node.expires = time.Now().Add(caseCacheTimeout) + if err != nil { + node.err = err return node } - node := v.(*caseNode) - if node.expires.Before(time.Now()) { - node = newCaseNode() - c.Add(key, node) + + num := len(dirNames) + node.children = make(map[string]struct{}, num) + node.lowerToReal = make(map[string]string, num) + lastLower := "" + for _, n := range dirNames { + node.children[n] = struct{}{} + lower := UnicodeLowercaseNormalized(n) + if lower != lastLower { + node.lowerToReal[lower] = n + lastLower = n + } } + return node }