fix(fs): store getExpireAdd mutex in caseCache (fixes #9836) (#10430)

In #9701 there was a change that put the mutex used for `getExpireAdd` directly in `defaultRealCaser`, which is erroneous because multiple filesystems can share the same `caseCache`.

### Purpose

Fixes #9836 and [Slow sync sending files from Android](https://forum.syncthing.net/t/slow-sync-sending-files-from-android/24208?u=marbens). There may be other issues caused by `getExpireAdd` conflicting with itself, though.

### Testing

Unit tests pass and the case cache and conflict detection _seem_ to behave correctly.

Signed-off-by: Marcus B Spencer <marcus@marcusspencer.us>
This commit is contained in:
Marcus B Spencer
2025-10-18 21:56:03 +02:00
committed by GitHub
parent 20d2406a0e
commit b4565c87ee
+17 -12
View File
@@ -397,10 +397,13 @@ func (f *caseFilesystem) checkCaseExisting(name string) error {
type defaultRealCaser struct {
cache *caseCache
fs Filesystem
mut sync.Mutex
}
type caseCache = lru.TwoQueueCache[string, *caseNode]
type caseCache struct {
*lru.TwoQueueCache[string, *caseNode]
mut sync.Mutex
}
func newCaseCache() *caseCache {
cache, err := lru.New2Q[string, *caseNode](caseCacheItemLimit)
@@ -408,7 +411,9 @@ func newCaseCache() *caseCache {
if err != nil {
panic(err)
}
return cache
return &caseCache{
TwoQueueCache: cache,
}
}
func (r *defaultRealCaser) realCase(name string) (string, error) {
@@ -418,7 +423,7 @@ func (r *defaultRealCaser) realCase(name string) (string, error) {
}
for _, comp := range PathComponents(name) {
node := r.getExpireAdd(realName)
node := r.cache.getExpireAdd(realName, r.fs)
if node.err != nil {
return "", node.err
@@ -444,18 +449,18 @@ func (r *defaultRealCaser) dropCache() {
// 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 (r *defaultRealCaser) getExpireAdd(key string) *caseNode {
r.mut.Lock()
defer r.mut.Unlock()
node, ok := r.cache.Get(key)
func (c *caseCache) getExpireAdd(key string, fs Filesystem) *caseNode {
c.mut.Lock()
defer c.mut.Unlock()
node, ok := c.Get(key)
if !ok {
node := newCaseNode(key, r.fs)
r.cache.Add(key, node)
node := newCaseNode(key, fs)
c.Add(key, node)
return node
}
if node.expires.Before(time.Now()) {
node = newCaseNode(key, r.fs)
r.cache.Add(key, node)
node = newCaseNode(key, fs)
c.Add(key, node)
}
return node
}