vfs: rework directory virtuals to calculate them in advance

This had the advantage that they will never be stale.
This commit is contained in:
Nick Craig-Wood 2023-07-17 21:05:41 +01:00
parent 5910ba5aa9
commit 064b9af95a

View File

@ -39,7 +39,7 @@ type Dir struct {
modTimeMu sync.Mutex // protects the following modTimeMu sync.Mutex // protects the following
modTime time.Time modTime time.Time
_hasVirtual atomic.Bool // shows if the directory has virtual entries _childVirtuals atomic.Int32 // non zero if any children have virtual directory entries
} }
//go:generate stringer -type=vState //go:generate stringer -type=vState
@ -66,7 +66,6 @@ func newDir(vfs *VFS, f fs.Fs, parent *Dir, fsDir fs.Directory) *Dir {
items: make(map[string]Node), items: make(map[string]Node),
} }
d.cleanupTimer = time.AfterFunc(vfs.Opt.DirCacheTime*2, d.cacheCleanup) d.cleanupTimer = time.AfterFunc(vfs.Opt.DirCacheTime*2, d.cacheCleanup)
d.setHasVirtual(false)
return d return d
} }
@ -197,32 +196,58 @@ func (d *Dir) Node() Node {
return d return d
} }
// hasVirtual returns whether the directory has virtual entries // hasVirtuals returns whether the directory has virtual entries
func (d *Dir) hasVirtual() bool { func (d *Dir) hasVirtuals() bool {
return d._hasVirtual.Load() return d._childVirtuals.Load() != 0
} }
// setHasVirtual sets the hasVirtual flag for the directory // getVirtuals returns the number of virtual entries in this and children
func (d *Dir) setHasVirtual(hasVirtual bool) { func (d *Dir) getVirtuals() int32 {
d._hasVirtual.Store(hasVirtual) return d._childVirtuals.Load()
}
// addVirtuals increments or decrements the number of virtual
// directories by the amount given in this and all the parent
// directories.
func (d *Dir) addVirtuals(inc int32) {
for {
d._childVirtuals.Add(inc)
d.mu.RLock()
parent := d.parent
d.mu.RUnlock()
if parent == nil {
break
}
d = parent
}
}
// _addVirtuals increments or decrements the number of virtual
// directories by the amount given in this and all the parent
// directories.
//
// The dir lock must be held to call this
func (d *Dir) _addVirtuals(inc int32) {
d._childVirtuals.Add(inc)
if d.parent == nil {
return
}
d.parent.addVirtuals(inc)
} }
// ForgetAll forgets directory entries for this directory and any children. // ForgetAll forgets directory entries for this directory and any children.
// //
// It does not invalidate or clear the cache of the parent directory. // It does not invalidate or clear the cache of the parent directory.
// //
// It returns true if the directory or any of its children had virtual entries // Directories or parents of directories with virtual entries won't be
// so could not be forgotten. Children which didn't have virtual entries and // forgotten.
// children with virtual entries will be forgotten even if true is returned. func (d *Dir) ForgetAll() {
func (d *Dir) ForgetAll() (hasVirtual bool) {
d.mu.RLock() d.mu.RLock()
fs.Debugf(d.path, "forgetting directory cache") fs.Debugf(d.path, "forgetting directory cache")
for _, node := range d.items { for _, node := range d.items {
if dir, ok := node.(*Dir); ok { if dir, ok := node.(*Dir); ok {
if dir.ForgetAll() { dir.ForgetAll()
d.setHasVirtual(true)
}
} }
} }
@ -236,19 +261,16 @@ func (d *Dir) ForgetAll() (hasVirtual bool) {
d.read = time.Time{} d.read = time.Time{}
// Check if this dir has virtual entries
if len(d.virtual) != 0 {
d.setHasVirtual(true)
}
// Don't clear directory entries if there are virtual entries in this // Don't clear directory entries if there are virtual entries in this
// directory or any children // directory or any children
if !d.hasVirtual() { if d.hasVirtuals() {
d.items = make(map[string]Node) d.cleanupTimer.Reset(d.vfs.Opt.DirCacheTime * 2)
d.cleanupTimer.Stop() return
} }
return d.hasVirtual() // Forget the items and stop the timer
d.items = make(map[string]Node)
d.cleanupTimer.Stop()
} }
// forgetDirPath clears the cache for itself and all subdirectories if // forgetDirPath clears the cache for itself and all subdirectories if
@ -393,6 +415,9 @@ func (d *Dir) renameTree(dirPath string) {
// reading everything again // reading everything again
func (d *Dir) rename(newParent *Dir, fsDir fs.Directory) { func (d *Dir) rename(newParent *Dir, fsDir fs.Directory) {
d.ForgetAll() d.ForgetAll()
virtuals := d.getVirtuals()
d.addVirtuals(-virtuals)
newParent.addVirtuals(virtuals)
d.modTimeMu.Lock() d.modTimeMu.Lock()
d.modTime = fsDir.ModTime(context.TODO()) d.modTime = fsDir.ModTime(context.TODO())
@ -429,13 +454,13 @@ func (d *Dir) addObject(node Node) {
d.items[leaf] = node d.items[leaf] = node
if d.virtual == nil { if d.virtual == nil {
d.virtual = make(map[string]vState) d.virtual = make(map[string]vState)
d._addVirtuals(1)
} }
vAdd := vAddFile vAdd := vAddFile
if node.IsDir() { if node.IsDir() {
vAdd = vAddDir vAdd = vAddDir
} }
d.virtual[leaf] = vAdd d.virtual[leaf] = vAdd
d.setHasVirtual(true)
fs.Debugf(d.path, "Added virtual directory entry %v: %q", vAdd, leaf) fs.Debugf(d.path, "Added virtual directory entry %v: %q", vAdd, leaf)
d.mu.Unlock() d.mu.Unlock()
} }
@ -478,9 +503,9 @@ func (d *Dir) delObject(leaf string) {
delete(d.items, leaf) delete(d.items, leaf)
if d.virtual == nil { if d.virtual == nil {
d.virtual = make(map[string]vState) d.virtual = make(map[string]vState)
d._addVirtuals(1)
} }
d.virtual[leaf] = vDel d.virtual[leaf] = vDel
d.setHasVirtual(true)
fs.Debugf(d.path, "Added virtual directory entry %v: %q", vDel, leaf) fs.Debugf(d.path, "Added virtual directory entry %v: %q", vDel, leaf)
d.mu.Unlock() d.mu.Unlock()
} }
@ -540,7 +565,7 @@ func (d *Dir) _deleteVirtual(name string) {
delete(d.virtual, name) delete(d.virtual, name)
if len(d.virtual) == 0 { if len(d.virtual) == 0 {
d.virtual = nil d.virtual = nil
d.setHasVirtual(false) d._addVirtuals(-1)
} }
fs.Debugf(d.path, "Removed virtual directory entry %v: %q", virtualState, name) fs.Debugf(d.path, "Removed virtual directory entry %v: %q", virtualState, name)
} }