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:
48
vfs/dir.go
48
vfs/dir.go
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user