summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/fusefrontend/dircache.go125
1 files changed, 86 insertions, 39 deletions
diff --git a/internal/fusefrontend/dircache.go b/internal/fusefrontend/dircache.go
index 5ae6d6b..9039f41 100644
--- a/internal/fusefrontend/dircache.go
+++ b/internal/fusefrontend/dircache.go
@@ -11,40 +11,60 @@ import (
"github.com/rfjakob/gocryptfs/internal/tlog"
)
-type dirCacheStruct struct {
- sync.Mutex
+const (
+ // Number of entries in the dirCache. Three entries work well for two
+ // (probably also three) parallel tar extracts (hit rate around 92%).
+ dirCacheSize = 3
+ // Enable Lookup/Store/Clear debug messages
+ enableDebugMessages = false
+ // Enable hit rate statistics printing
+ enableStats = true
+)
+
+type dirCacheEntryStruct struct {
// relative plaintext path to the directory
dirRelPath string
// fd to the directory (opened with O_PATH!)
fd int
// content of gocryptfs.diriv in this directory
iv []byte
- // on the first Lookup(), the expire thread is stared, and this is set
+}
+
+func (e *dirCacheEntryStruct) Clear() {
+ // An earlier clear may have already closed the fd, or the cache
+ // has never been filled (fd is 0 in that case).
+ if e.fd > 0 {
+ err := syscall.Close(e.fd)
+ if err != nil {
+ tlog.Warn.Printf("dirCache.Clear: Close failed: %v", err)
+ }
+ }
+ e.fd = -1
+ e.dirRelPath = ""
+ e.iv = nil
+}
+
+type dirCacheStruct struct {
+ sync.Mutex
+ // Cache entries
+ entries [dirCacheSize]dirCacheEntryStruct
+ // Where to store the next entry (index into entries)
+ nextIndex int
+ // On the first Lookup(), the expire thread is stared, and this flag is set
// to true.
expireThreadRunning bool
+ // Hit rate stats. Evaluated and reset by the expire thread.
+ lookups uint64
+ hits uint64
}
// Clear clears the cache contents.
func (d *dirCacheStruct) Clear() {
d.Lock()
defer d.Unlock()
- d.doClear()
-}
-
-// doClear closes the fd and clears the cache contents.
-// Caller must hold d.Lock()!
-func (d *dirCacheStruct) doClear() {
- // An earlier clear may have already closed the fd, or the cache
- // has never been filled (fd is 0 in that case).
- if d.fd > 0 {
- err := syscall.Close(d.fd)
- if err != nil {
- tlog.Warn.Printf("dirCache.Clear: Close failed: %v", err)
- }
+ for i := range d.entries {
+ d.entries[i].Clear()
}
- d.fd = -1
- d.dirRelPath = ""
- d.iv = nil
}
// Store the entry in the cache. The passed "fd" will be Dup()ed, and the caller
@@ -55,17 +75,20 @@ func (d *dirCacheStruct) Store(dirRelPath string, fd int, iv []byte) {
}
d.Lock()
defer d.Unlock()
+ e := &d.entries[d.nextIndex]
+ // Round-robin works well enough
+ d.nextIndex = (d.nextIndex + 1) % dirCacheSize
// Close the old fd
- d.doClear()
+ e.Clear()
fd2, err := syscall.Dup(fd)
if err != nil {
tlog.Warn.Printf("dirCache.Store: Dup failed: %v", err)
return
}
- d.fd = fd2
d.dbg("Store: %q %d %x\n", dirRelPath, fd2, iv)
- d.dirRelPath = dirRelPath
- d.iv = iv
+ e.fd = fd2
+ e.dirRelPath = dirRelPath
+ e.iv = iv
// expireThread is started on the first Lookup()
if !d.expireThreadRunning {
d.expireThreadRunning = true
@@ -73,31 +96,45 @@ func (d *dirCacheStruct) Store(dirRelPath string, fd int, iv []byte) {
}
}
-// Lookup checks if relPath is in the cache, and returns and (fd, iv) pair.
+// Lookup checks if relPath is in the cache, and returns an (fd, iv) pair.
// It returns (-1, nil) if not found. The fd is internally Dup()ed and the
// caller must close it when done.
func (d *dirCacheStruct) Lookup(dirRelPath string) (fd int, iv []byte) {
d.Lock()
defer d.Unlock()
- if d.fd <= 0 {
- // Cache is empty
- d.dbg("Lookup %q: empty\n", dirRelPath)
- return -1, nil
+ if enableStats {
+ d.lookups++
+ }
+ for i := range d.entries {
+ e := &d.entries[i]
+ if e.fd <= 0 {
+ // Cache slot is empty
+ continue
+ }
+ if dirRelPath != e.dirRelPath {
+ // Not the right path
+ continue
+ }
+ var err error
+ fd, err = syscall.Dup(e.fd)
+ if err != nil {
+ tlog.Warn.Printf("dirCache.Lookup: Dup failed: %v", err)
+ return -1, nil
+ }
+ iv = e.iv
}
- if dirRelPath != d.dirRelPath {
+ if fd == 0 {
d.dbg("Lookup %q: miss\n", dirRelPath)
return -1, nil
}
- fd, err := syscall.Dup(d.fd)
- if err != nil {
- tlog.Warn.Printf("dirCache.Lookup: Dup failed: %v", err)
- return -1, nil
+ if enableStats {
+ d.hits++
}
- if fd <= 0 || len(d.iv) != nametransform.DirIVLen {
- log.Panicf("Lookup sanity check failed: fd=%d len=%d", fd, len(d.iv))
+ if fd <= 0 || len(iv) != nametransform.DirIVLen {
+ log.Panicf("Lookup sanity check failed: fd=%d len=%d", fd, len(iv))
}
- d.dbg("Lookup %q: hit %d %x\n", dirRelPath, fd, d.iv)
- return fd, d.iv
+ d.dbg("Lookup %q: hit %d %x\n", dirRelPath, fd, iv)
+ return fd, iv
}
// expireThread is started on the first Lookup()
@@ -105,13 +142,23 @@ func (d *dirCacheStruct) expireThread() {
for {
time.Sleep(1 * time.Second)
d.Clear()
+ if enableStats {
+ d.Lock()
+ lookups := d.lookups
+ hits := d.hits
+ d.lookups = 0
+ d.hits = 0
+ d.Unlock()
+ if lookups > 0 {
+ fmt.Printf("dirCache: hits=%3d lookups=%3d, rate=%3d%%\n", hits, lookups, (hits*100)/lookups)
+ }
+ }
}
}
// dbg prints a debug message. Usually disabled.
func (d *dirCacheStruct) dbg(format string, a ...interface{}) {
- const EnableDebugMessages = false
- if EnableDebugMessages {
+ if enableDebugMessages {
fmt.Printf(format, a...)
}
}