瀏覽代碼

lib/fs, lib/model: Reduce lock contention on NewCaseFilesystem (fixes #7268) (#7269)

Jakob Borg 4 年之前
父節點
當前提交
8f199e12b3
共有 2 個文件被更改,包括 33 次插入20 次删除
  1. 23 13
      lib/fs/casefs.go
  2. 10 7
      lib/model/model.go

+ 23 - 13
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()
 	}
 }
 

+ 10 - 7
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 {