1
0
mirror of https://github.com/rclone/rclone.git synced 2025-12-15 15:53:41 +00:00

vfs: revise locking in file and dir to fix race conditions

This commit is contained in:
Nick Craig-Wood
2019-11-06 21:48:43 +00:00
parent e0d9314059
commit 51efb349ac
2 changed files with 120 additions and 67 deletions

View File

@@ -20,14 +20,15 @@ import (
// Dir represents a directory entry
type Dir struct {
vfs *VFS
inode uint64 // inode number
f fs.Fs
parent *Dir // parent, nil for root
vfs *VFS // read only
inode uint64 // read only: inode number
f fs.Fs // read only
mu sync.RWMutex // protects the following
parent *Dir // parent, nil for root
path string
modTime time.Time
entry fs.Directory
mu sync.Mutex // protects the following
read time.Time // time directory entry last read
items map[string]Node // directory entries - can be empty but not nil
}
@@ -50,6 +51,8 @@ func (d *Dir) String() string {
if d == nil {
return "<nil *Dir>"
}
d.mu.RLock()
defer d.mu.RUnlock()
return d.path + "/"
}
@@ -70,7 +73,9 @@ func (d *Dir) Mode() (mode os.FileMode) {
// Name (base) of the directory - satisfies Node interface
func (d *Dir) Name() (name string) {
d.mu.RLock()
name = path.Base(d.path)
d.mu.RUnlock()
if name == "." {
name = "/"
}
@@ -79,6 +84,8 @@ func (d *Dir) Name() (name string) {
// Path of the directory - satisfies Node interface
func (d *Dir) Path() (name string) {
d.mu.RLock()
defer d.mu.RUnlock()
return d.path
}
@@ -105,6 +112,7 @@ func (d *Dir) Node() Node {
func (d *Dir) forgetDirPath(relativePath string) {
if dir := d.cachedDir(relativePath); dir != nil {
dir.walk(func(dir *Dir) {
// this is called with the mutex held
fs.Debugf(dir.path, "forgetting directory cache")
dir.read = time.Time{}
dir.items = make(map[string]Node)
@@ -139,7 +147,9 @@ func (d *Dir) invalidateDir(absPath string) {
// if entryType is a directory it invalidates the parent of the directory too.
func (d *Dir) changeNotify(relativePath string, entryType fs.EntryType) {
defer log.Trace(d.path, "relativePath=%q, type=%v", relativePath, entryType)("")
d.mu.RLock()
absPath := path.Join(d.path, relativePath)
d.mu.RUnlock()
d.invalidateDir(findParent(absPath))
if entryType == fs.EntryDirectory {
d.invalidateDir(absPath)
@@ -154,7 +164,10 @@ func (d *Dir) changeNotify(relativePath string, entryType fs.EntryType) {
// you cannot clear the cache for the Dir's ancestors or siblings.
func (d *Dir) ForgetPath(relativePath string, entryType fs.EntryType) {
defer log.Trace(d.path, "relativePath=%q, type=%v", relativePath, entryType)("")
if absPath := path.Join(d.path, relativePath); absPath != "" {
d.mu.RLock()
absPath := path.Join(d.path, relativePath)
d.mu.RUnlock()
if absPath != "" {
d.invalidateDir(findParent(absPath))
}
if entryType == fs.EntryDirectory {
@@ -164,6 +177,8 @@ func (d *Dir) ForgetPath(relativePath string, entryType fs.EntryType) {
// walk runs a function on all cached directories. It will be called
// on a directory's children first.
//
// The mutex will be held for the directory when fun is called
func (d *Dir) walk(fun func(*Dir)) {
d.mu.Lock()
defer d.mu.Unlock()
@@ -176,18 +191,11 @@ func (d *Dir) walk(fun func(*Dir)) {
fun(d)
}
// stale returns true if the directory contents will be read the next
// time it is accessed. stale must be called with d.mu held.
func (d *Dir) stale(when time.Time) bool {
_, stale := d.age(when)
return stale
}
// age returns the duration since the last time the directory contents
// was read and the content is cosidered stale. age will be 0 and
// stale true if the last read time is empty.
// age must be called with d.mu held.
func (d *Dir) age(when time.Time) (age time.Duration, stale bool) {
func (d *Dir) _age(when time.Time) (age time.Duration, stale bool) {
if d.read.IsZero() {
return age, true
}
@@ -202,11 +210,13 @@ func (d *Dir) age(when time.Time) (age time.Duration, stale bool) {
// reading everything again
func (d *Dir) rename(newParent *Dir, fsDir fs.Directory) {
d.ForgetAll()
d.mu.Lock()
d.parent = newParent
d.entry = fsDir
d.path = fsDir.Remote()
d.modTime = fsDir.ModTime(context.TODO())
d.read = time.Time{}
d.mu.Unlock()
}
// addObject adds a new object or directory to the directory
@@ -228,7 +238,7 @@ func (d *Dir) delObject(leaf string) {
// read the directory and sets d.items - must be called with the lock held
func (d *Dir) _readDir() error {
when := time.Now()
if age, stale := d.age(when); stale {
if age, stale := d._age(when); stale {
if age != 0 {
fs.Debugf(d.path, "Re-reading directory (%v old)", age)
}
@@ -317,9 +327,9 @@ func (d *Dir) _readDirFromEntries(entries fs.DirEntries, dirTree dirtree.DirTree
// readDirTree forces a refresh of the complete directory tree
func (d *Dir) readDirTree() error {
d.mu.Lock()
d.mu.RLock()
f, path := d.f, d.path
d.mu.Unlock()
d.mu.RUnlock()
when := time.Now()
fs.Debugf(path, "Reading directory tree")
dt, err := walk.NewDirTree(context.TODO(), f, path, false, -1)
@@ -394,6 +404,8 @@ func (d *Dir) isEmpty() (bool, error) {
// ModTime returns the modification time of the directory
func (d *Dir) ModTime() time.Time {
d.mu.RLock()
defer d.mu.RUnlock()
// fs.Debugf(d.path, "Dir.ModTime %v", d.modTime)
return d.modTime
}
@@ -409,8 +421,8 @@ func (d *Dir) SetModTime(modTime time.Time) error {
return EROFS
}
d.mu.Lock()
defer d.mu.Unlock()
d.modTime = modTime
d.mu.Unlock()
return nil
}