diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/fusefrontend/dircache.go | 125 | 
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...)  	}  } | 
