From 675bc4d1252a9be91aa611d68a62c7049f9d344a Mon Sep 17 00:00:00 2001 From: ston1th Date: Sun, 20 Mar 2022 10:19:03 +0100 Subject: [PATCH] added stat cache --- pkg/fs/fs.go | 61 +++++++++++++++++++++++++++-------------------- pkg/fs/stat.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 25 deletions(-) create mode 100644 pkg/fs/stat.go diff --git a/pkg/fs/fs.go b/pkg/fs/fs.go index e9d832d..f8092db 100644 --- a/pkg/fs/fs.go +++ b/pkg/fs/fs.go @@ -22,18 +22,20 @@ import ( ) type FS struct { - mu sync.RWMutex - log logr.Logger - NoCache http.Handler - src string - dst string - mdf *os.File - jenc *json.Encoder - dc *DirCache - q *Quota - md MetadataMap - pm map[string]func() - cancel func() + mu sync.RWMutex + log logr.Logger + NoCache http.Handler + src string + dst string + statCancel func() + flushCancel func() + mdf *os.File + jenc *json.Encoder + dc *DirCache + sc *StatCache + q *Quota + md MetadataMap + pm map[string]func() } func NewFS(max int64, src, dst, metadata string, log logr.Logger) (fs *FS, err error) { @@ -53,16 +55,21 @@ func NewFS(max int64, src, dst, metadata string, log logr.Logger) (fs *FS, err e if err != nil { return } + statCtx, statCancel := context.WithCancel(context.Background()) + flushCtx, flushCancel := context.WithCancel(context.Background()) fs = &FS{ - log: log, - NoCache: http.FileServer(http.Dir(src)), - src: src, - dst: dst, - mdf: mdf, - dc: NewDirCache(), - md: make(MetadataMap), - pm: make(map[string]func()), - jenc: json.NewEncoder(mdf), + log: log, + NoCache: http.FileServer(http.Dir(src)), + src: src, + dst: dst, + mdf: mdf, + statCancel: statCancel, + flushCancel: flushCancel, + dc: NewDirCache(), + sc: NewStatCache(statCtx), + md: make(MetadataMap), + pm: make(map[string]func()), + jenc: json.NewEncoder(mdf), } fs.q, err = NewQuota(max, fs, fs.log.WithName("quota")) if err != nil { @@ -74,9 +81,7 @@ func NewFS(max int64, src, dst, metadata string, log logr.Logger) (fs *FS, err e } fs.initMetadata() fs.q.Init() - ctx, cancel := context.WithCancel(context.Background()) - fs.cancel = cancel - go fs.flusher(ctx) + go fs.flusher(flushCtx) return fs, err } @@ -110,8 +115,13 @@ func (fs *FS) Stat(name string) (fi stdfs.FileInfo, err error) { func (fs *FS) StatWithOffline(name string) (fi stdfs.FileInfo, offline bool, err error) { sp, dp := fs.paths(name) + fi, err = fs.sc.Get(name) + if err == nil { + return + } fi, err = os.Stat(sp) if err == nil { + fs.sc.Set(name, fi) return } fi, err = os.Stat(dp) @@ -347,7 +357,8 @@ func (fs *FS) flushMetadata() { } func (fs *FS) Close() { - fs.cancel() + fs.flushCancel() + fs.statCancel() for _, cancel := range fs.pm { cancel() } diff --git a/pkg/fs/stat.go b/pkg/fs/stat.go new file mode 100644 index 0000000..841870f --- /dev/null +++ b/pkg/fs/stat.go @@ -0,0 +1,64 @@ +// Copyright (C) 2022 Marius Schellenberger + +package fs + +import ( + "context" + "os" + "sync" + "time" +) + +const statExpiry = time.Minute * 5 + +type statCacheEntry struct { + mtime time.Time + fi os.FileInfo +} + +type StatCache struct { + mu sync.RWMutex + ctx context.Context + m map[string]*statCacheEntry +} + +func NewStatCache(ctx context.Context) *StatCache { + sc := &StatCache{ctx: ctx, m: make(map[string]*statCacheEntry)} + go sc.cleanup() + return sc +} + +func (sc *StatCache) cleanup() { + t := time.NewTicker(statExpiry) + defer t.Stop() + for { + select { + case <-sc.ctx.Done(): + return + case <-t.C: + } + sc.mu.Lock() + now := time.Now() + for k, sce := range sc.m { + if now.After(sce.mtime) { + delete(sc.m, k) + } + } + sc.mu.Unlock() + } +} + +func (sc *StatCache) Get(name string) (os.FileInfo, error) { + sc.mu.RLock() + defer sc.mu.RUnlock() + if sce, ok := sc.m[name]; ok { + return sce.fi, nil + } + return nil, NoCacheEntry +} + +func (sc *StatCache) Set(name string, fi os.FileInfo) { + sc.mu.Lock() + defer sc.mu.Unlock() + sc.m[name] = &statCacheEntry{time.Now().Add(statExpiry), fi} +}