diff --git a/lib/fs/casefs.go b/lib/fs/casefs.go index b755ac6ff..b2aab9151 100644 --- a/lib/fs/casefs.go +++ b/lib/fs/casefs.go @@ -48,25 +48,35 @@ type fskey struct { // their cache every now and then. type caseFilesystemRegistry struct { fss map[fskey]*caseFilesystem - mut sync.Mutex + mut sync.RWMutex startCleaner sync.Once } func (r *caseFilesystemRegistry) get(fs Filesystem) Filesystem { - r.mut.Lock() - defer r.mut.Unlock() - k := fskey{fs.Type(), fs.URI()} + + // Use double locking when getting a caseFs. In the common case it will + // already exist and we take the read lock fast path. If it doesn't, we + // take a write lock and try again. + + r.mut.RLock() caseFs, ok := r.fss[k] + r.mut.RUnlock() + if !ok { - caseFs = &caseFilesystem{ - Filesystem: fs, - realCaser: newDefaultRealCaser(fs), + r.mut.Lock() + caseFs, ok = r.fss[k] + if !ok { + caseFs = &caseFilesystem{ + Filesystem: fs, + realCaser: newDefaultRealCaser(fs), + } + r.fss[k] = caseFs + r.startCleaner.Do(func() { + go r.cleaner() + }) } - r.fss[k] = caseFs - r.startCleaner.Do(func() { - go r.cleaner() - }) + r.mut.Unlock() } return caseFs @@ -74,11 +84,11 @@ func (r *caseFilesystemRegistry) get(fs Filesystem) Filesystem { func (r *caseFilesystemRegistry) cleaner() { for range time.NewTicker(time.Minute).C { - r.mut.Lock() + r.mut.RLock() for _, caseFs := range r.fss { caseFs.dropCache() } - r.mut.Unlock() + r.mut.RUnlock() } } diff --git a/lib/model/model.go b/lib/model/model.go index ce1e88fac..0de3f82f5 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -1790,13 +1790,6 @@ func (m *model) Request(deviceID protocol.DeviceID, folder, name string, blockNo return nil, protocol.ErrInvalid } - folderFs := folderCfg.Filesystem() - - if err := osutil.TraversesSymlink(folderFs, filepath.Dir(name)); err != nil { - l.Debugf("%v REQ(in) traversal check: %s - %s: %q / %q o=%d s=%d", m, err, deviceID, folder, name, offset, size) - return nil, protocol.ErrNoSuchFile - } - // Restrict parallel requests by connection/device m.pmut.RLock() @@ -1814,6 +1807,16 @@ func (m *model) Request(deviceID protocol.DeviceID, folder, name string, blockNo } }() + // Grab the FS after limiting, as it causes I/O and we want to minimize + // the race time between the symlink check and the read. + + folderFs := folderCfg.Filesystem() + + if err := osutil.TraversesSymlink(folderFs, filepath.Dir(name)); err != nil { + l.Debugf("%v REQ(in) traversal check: %s - %s: %q / %q o=%d s=%d", m, err, deviceID, folder, name, offset, size) + return nil, protocol.ErrNoSuchFile + } + // Only check temp files if the flag is set, and if we are set to advertise // the temp indexes. if fromTemporary && !folderCfg.DisableTempIndexes {