Files
kopia/fuse/cache.go
2016-09-25 16:27:28 -07:00

157 lines
2.9 KiB
Go

// +build !windows
// Package fuse implements FUSE filesystem nodes for mounting contents of filesystem stored in repository.
//
// The FUSE implementation used is from bazil.org/fuse
package fuse
import (
"sync"
"sync/atomic"
"github.com/kopia/kopia/fs"
)
type cacheEntry struct {
id int64
prev *cacheEntry
next *cacheEntry
entries fs.Entries
}
// Cache maintains in-memory cache of recently-read data to speed up filesystem operations.
type Cache struct {
sync.Mutex
nextID int64
totalDirectoryEntries int
maxDirectories int
maxDirectoryEntries int
data map[int64]*cacheEntry
// Doubly-linked list of entries, in access time order
head *cacheEntry
tail *cacheEntry
}
func (c *Cache) allocateID() int64 {
if c == nil {
return 0
}
return atomic.AddInt64(&c.nextID, 1)
}
func (c *Cache) moveToHead(e *cacheEntry) {
if e == c.head {
// Already at head, no change.
return
}
c.remove(e)
c.addToHead(e)
}
func (c *Cache) addToHead(e *cacheEntry) {
if c.head != nil {
e.next = c.head
c.head.prev = e
c.head = e
} else {
c.head = e
c.tail = e
}
}
func (c *Cache) remove(e *cacheEntry) {
if e.prev == nil {
// First element.
c.head = e.next
} else {
e.prev.next = e.next
}
if e.next == nil {
// Last element
c.tail = e.prev
} else {
e.next.prev = e.prev
}
}
func (c *Cache) getEntries(id int64, cb func() (fs.Entries, error)) (fs.Entries, error) {
if c == nil {
return cb()
}
c.Lock()
if v, ok := c.data[id]; ok {
c.moveToHead(v)
c.Unlock()
return v.entries, nil
}
raw, err := cb()
if err != nil {
return nil, err
}
if len(raw) > c.maxDirectoryEntries {
// no point caching since it would not fit anyway, just return it.
c.Unlock()
return raw, nil
}
entry := &cacheEntry{
id: id,
entries: raw,
}
c.addToHead(entry)
c.data[id] = entry
c.totalDirectoryEntries += len(raw)
for c.totalDirectoryEntries > c.maxDirectoryEntries || len(c.data) > c.maxDirectories {
toremove := c.tail
c.remove(toremove)
c.totalDirectoryEntries -= len(toremove.entries)
delete(c.data, toremove.id)
}
c.Unlock()
return raw, nil
}
// CacheOption modifies the behavior of FUSE node cache.
type CacheOption func(c *Cache)
// MaxCachedDirectories configures cache to allow at most the given number of cached directories.
func MaxCachedDirectories(count int) CacheOption {
return func(c *Cache) {
c.maxDirectories = count
}
}
// MaxCachedDirectoryEntries configures cache to allow at most the given number entries in cached directories.
func MaxCachedDirectoryEntries(count int) CacheOption {
return func(c *Cache) {
c.maxDirectoryEntries = count
}
}
// NewCache creates FUSE node cache.
func NewCache(options ...CacheOption) *Cache {
c := &Cache{
data: make(map[int64]*cacheEntry),
maxDirectories: 1000,
maxDirectoryEntries: 100000,
}
for _, o := range options {
o(c)
}
return c
}