small refactor*
This commit is contained in:
parent
b6b541e050
commit
24a4d30275
232 changed files with 2164 additions and 1906 deletions
server/pkg/go-nfs/helpers
199
server/pkg/go-nfs/helpers/cachinghandler.go
Normal file
199
server/pkg/go-nfs/helpers/cachinghandler.go
Normal file
|
@ -0,0 +1,199 @@
|
|||
package helpers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"io/fs"
|
||||
"reflect"
|
||||
|
||||
"git.kmsign.ru/royalcat/tstor/server/pkg/go-nfs"
|
||||
|
||||
"github.com/google/uuid"
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
)
|
||||
|
||||
// NewCachingHandler wraps a handler to provide a basic to/from-file handle cache.
|
||||
func NewCachingHandler(h nfs.Handler, limit int) nfs.Handler {
|
||||
return NewCachingHandlerWithVerifierLimit(h, limit, limit)
|
||||
}
|
||||
|
||||
// NewCachingHandlerWithVerifierLimit provides a basic to/from-file handle cache that can be tuned with a smaller cache of active directory listings.
|
||||
func NewCachingHandlerWithVerifierLimit(h nfs.Handler, limit int, verifierLimit int) nfs.Handler {
|
||||
if limit < 2 || verifierLimit < 2 {
|
||||
nfs.Log.Warnf("Caching handler created with insufficient cache to support directory listing", "size", limit, "verifiers", verifierLimit)
|
||||
}
|
||||
cache, _ := lru.New[uuid.UUID, entry](limit)
|
||||
reverseCache := make(map[string][]uuid.UUID)
|
||||
verifiers, _ := lru.New[uint64, verifier](verifierLimit)
|
||||
return &CachingHandler{
|
||||
Handler: h,
|
||||
activeHandles: cache,
|
||||
reverseHandles: reverseCache,
|
||||
activeVerifiers: verifiers,
|
||||
cacheLimit: limit,
|
||||
}
|
||||
}
|
||||
|
||||
// CachingHandler implements to/from handle via an LRU cache.
|
||||
type CachingHandler struct {
|
||||
nfs.Handler
|
||||
activeHandles *lru.Cache[uuid.UUID, entry]
|
||||
reverseHandles map[string][]uuid.UUID
|
||||
activeVerifiers *lru.Cache[uint64, verifier]
|
||||
cacheLimit int
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
f nfs.Filesystem
|
||||
p []string
|
||||
}
|
||||
|
||||
// ToHandle takes a file and represents it with an opaque handle to reference it.
|
||||
// In stateless nfs (when it's serving a unix fs) this can be the device + inode
|
||||
// but we can generalize with a stateful local cache of handed out IDs.
|
||||
func (c *CachingHandler) ToHandle(ctx context.Context, f nfs.Filesystem, path []string) []byte {
|
||||
joinedPath := f.Join(path...)
|
||||
|
||||
if handle := c.searchReverseCache(f, joinedPath); handle != nil {
|
||||
return handle
|
||||
}
|
||||
|
||||
id := uuid.New()
|
||||
|
||||
newPath := make([]string, len(path))
|
||||
|
||||
copy(newPath, path)
|
||||
evictedKey, evictedPath, ok := c.activeHandles.GetOldest()
|
||||
if evicted := c.activeHandles.Add(id, entry{f, newPath}); evicted && ok {
|
||||
rk := evictedPath.f.Join(evictedPath.p...)
|
||||
c.evictReverseCache(rk, evictedKey)
|
||||
}
|
||||
|
||||
if _, ok := c.reverseHandles[joinedPath]; !ok {
|
||||
c.reverseHandles[joinedPath] = []uuid.UUID{}
|
||||
}
|
||||
c.reverseHandles[joinedPath] = append(c.reverseHandles[joinedPath], id)
|
||||
b, _ := id.MarshalBinary()
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// FromHandle converts from an opaque handle to the file it represents
|
||||
func (c *CachingHandler) FromHandle(ctx context.Context, fh []byte) (nfs.Filesystem, []string, error) {
|
||||
id, err := uuid.FromBytes(fh)
|
||||
if err != nil {
|
||||
return nil, []string{}, err
|
||||
}
|
||||
|
||||
if f, ok := c.activeHandles.Get(id); ok {
|
||||
for _, k := range c.activeHandles.Keys() {
|
||||
candidate, _ := c.activeHandles.Peek(k)
|
||||
if hasPrefix(f.p, candidate.p) {
|
||||
_, _ = c.activeHandles.Get(k)
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
newP := make([]string, len(f.p))
|
||||
copy(newP, f.p)
|
||||
return f.f, newP, nil
|
||||
}
|
||||
}
|
||||
return nil, []string{}, &nfs.NFSStatusError{NFSStatus: nfs.NFSStatusStale}
|
||||
}
|
||||
|
||||
func (c *CachingHandler) searchReverseCache(f nfs.Filesystem, path string) []byte {
|
||||
uuids, exists := c.reverseHandles[path]
|
||||
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, id := range uuids {
|
||||
if candidate, ok := c.activeHandles.Get(id); ok {
|
||||
if reflect.DeepEqual(candidate.f, f) {
|
||||
return id[:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CachingHandler) evictReverseCache(path string, handle uuid.UUID) {
|
||||
uuids, exists := c.reverseHandles[path]
|
||||
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
for i, u := range uuids {
|
||||
if u == handle {
|
||||
uuids = append(uuids[:i], uuids[i+1:]...)
|
||||
c.reverseHandles[path] = uuids
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CachingHandler) InvalidateHandle(ctx context.Context, fs nfs.Filesystem, handle []byte) error {
|
||||
//Remove from cache
|
||||
id, _ := uuid.FromBytes(handle)
|
||||
entry, ok := c.activeHandles.Get(id)
|
||||
if ok {
|
||||
rk := entry.f.Join(entry.p...)
|
||||
c.evictReverseCache(rk, id)
|
||||
}
|
||||
c.activeHandles.Remove(id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleLimit exports how many file handles can be safely stored by this cache.
|
||||
func (c *CachingHandler) HandleLimit() int {
|
||||
return c.cacheLimit
|
||||
}
|
||||
|
||||
func hasPrefix(path, prefix []string) bool {
|
||||
if len(prefix) > len(path) {
|
||||
return false
|
||||
}
|
||||
for i, e := range prefix {
|
||||
if path[i] != e {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type verifier struct {
|
||||
path string
|
||||
contents []fs.FileInfo
|
||||
}
|
||||
|
||||
func hashPathAndContents(path string, contents []fs.FileInfo) uint64 {
|
||||
//calculate a cookie-verifier.
|
||||
vHash := sha256.New()
|
||||
|
||||
// Add the path to avoid collisions of directories with the same content
|
||||
vHash.Write(binary.BigEndian.AppendUint64([]byte{}, uint64(len(path))))
|
||||
vHash.Write([]byte(path))
|
||||
|
||||
for _, c := range contents {
|
||||
vHash.Write([]byte(c.Name())) // Never fails according to the docs
|
||||
}
|
||||
|
||||
verify := vHash.Sum(nil)[0:8]
|
||||
return binary.BigEndian.Uint64(verify)
|
||||
}
|
||||
|
||||
func (c *CachingHandler) VerifierFor(path string, contents []fs.FileInfo) uint64 {
|
||||
id := hashPathAndContents(path, contents)
|
||||
c.activeVerifiers.Add(id, verifier{path, contents})
|
||||
return id
|
||||
}
|
||||
|
||||
func (c *CachingHandler) DataForVerifier(path string, id uint64) []fs.FileInfo {
|
||||
if cache, ok := c.activeVerifiers.Get(id); ok {
|
||||
return cache.contents
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue