From 2bd1bef586f91a6f7591d54492c4ae18d2128b1a Mon Sep 17 00:00:00 2001 From: MickaelK Date: Wed, 18 Mar 2026 13:37:01 +1100 Subject: [PATCH] fix (plg_backend_nfs): add a cache layer for nfs --- server/plugin/plg_backend_nfs/index.go | 100 ++++++++++++++++++++----- 1 file changed, 82 insertions(+), 18 deletions(-) diff --git a/server/plugin/plg_backend_nfs/index.go b/server/plugin/plg_backend_nfs/index.go index 62d6fef7..b3b28a57 100644 --- a/server/plugin/plg_backend_nfs/index.go +++ b/server/plugin/plg_backend_nfs/index.go @@ -1,11 +1,11 @@ package plg_backend_nfs import ( - "context" "io" "os" "path/filepath" "strings" + "sync" . "github.com/mickael-kerjean/filestash/server/common" @@ -15,11 +15,14 @@ import ( "github.com/vmware/go-nfs-client/nfs/xdr" ) +var NfsCache AppCache + type NfsShare struct { mount *nfs.Mount v *nfs.Target auth rpc.Auth - ctx context.Context + mu *sync.Mutex + wg *sync.WaitGroup uid uint32 gid uint32 gids []uint32 @@ -28,6 +31,22 @@ type NfsShare struct { func init() { Backend.Register("nfs", NfsShare{}) util.DefaultLogger.SetDebug(false) + + NfsCache = NewAppCache(2, 1) + NfsCache.OnEvict(func(key string, value interface{}) { + c := value.(*NfsShare) + if c == nil { + Log.Warning("plg_backend_nfs::nfs is nil on close") + return + } else if c.wg == nil { + c.Close() + Log.Warning("plg_backend_nfs::wg is nil on close") + return + } + c.wg.Wait() + Log.Debug("plg_backend_nfs::vacuum") + c.Close() + }) } func (this NfsShare) Init(params map[string]string, app *App) (IBackend, error) { @@ -38,9 +57,26 @@ func (this NfsShare) Init(params map[string]string, app *App) (IBackend, error) params["machine_name"] = "Filestash" } params["username"] = params["uid"] - uid, gid, gids := ExtractUserInfo(params["uid"], params["gid"], params["gids"]) - Log.Debug("plg_backend_nfs::userInfo user=%s uid=%d gid=%d gids=%+v", params["uid"], uid, gid, gids) + if c := NfsCache.Get(params); c != nil { + d := c.(*NfsShare) + if d == nil { + Log.Warning("plg_backend_nfs::nfs is nil on get") + return nil, ErrInternal + } else if d.wg == nil { + Log.Warning("plg_backend_nfs::wg is nil on get") + return nil, ErrInternal + } + d.wg.Add(1) + go func() { + <-app.Context.Done() + d.wg.Done() + }() + return d, nil + } + + uid, gid, gids := ExtractUserInfo(params["uid"], params["gid"], params["gids"]) + Log.Debug("plg_backend_nfs::userInfo user=%s uid=%d gid=%d gids=%v", params["uid"], uid, gid, gids) mount, err := nfs.DialMount(params["hostname"]) if err != nil { return nil, err @@ -53,7 +89,15 @@ func (this NfsShare) Init(params map[string]string, app *App) (IBackend, error) if err != nil { return nil, err } - return NfsShare{mount, v, auth, app.Context, uid, gid, toGids(gids)}, nil + + s := &NfsShare{mount, v, auth, new(sync.Mutex), new(sync.WaitGroup), uid, gid, toGids(gids)} + s.wg.Add(1) + go func() { + <-app.Context.Done() + s.wg.Done() + }() + NfsCache.Set(params, s) + return s, nil } func toGids(gids []GroupLabel) []uint32 { @@ -123,6 +167,8 @@ func (this NfsShare) LoginForm() Form { } func (this NfsShare) Meta(path string) Metadata { + this.mu.Lock() + defer this.mu.Unlock() f, _, err := this.v.Lookup(strings.TrimSuffix(path, "/")) if err != nil { return Metadata{} @@ -184,7 +230,8 @@ func isIn(id uint32, list []uint32) bool { } func (this NfsShare) Ls(path string) ([]os.FileInfo, error) { - defer this.Close() + this.mu.Lock() + defer this.mu.Unlock() files := make([]os.FileInfo, 0) dirs, err := this.v.ReadDirPlus(path) @@ -228,30 +275,46 @@ func (this NfsShare) Ls(path string) ([]os.FileInfo, error) { } func (this NfsShare) Stat(path string) (os.FileInfo, error) { - defer this.Close() return nil, ErrNotImplemented } func (this NfsShare) Cat(path string) (io.ReadCloser, error) { - go func() { - <-this.ctx.Done() - this.Close() - }() - rc, err := this.v.OpenFile(path, 0777) + this.mu.Lock() + rc, err := this.v.Open(path) + this.mu.Unlock() if err != nil { return nil, err } - return rc, nil + return &nfsReadCloser{rc, this.mu}, nil +} + +type nfsReadCloser struct { + io.ReadCloser + mu *sync.Mutex +} + +func (r *nfsReadCloser) Read(p []byte) (int, error) { + r.mu.Lock() + defer r.mu.Unlock() + return r.ReadCloser.Read(p) +} + +func (r *nfsReadCloser) Close() error { + r.mu.Lock() + defer r.mu.Unlock() + return r.ReadCloser.Close() } func (this NfsShare) Mkdir(path string) error { - defer this.Close() + this.mu.Lock() + defer this.mu.Unlock() _, err := this.v.Mkdir(this.nfsPath(path), 0775) return err } func (this NfsShare) Rm(path string) error { - defer this.Close() + this.mu.Lock() + defer this.mu.Unlock() if strings.HasSuffix(path, "/") { return this.v.RemoveAll(this.nfsPath(path)) } @@ -262,8 +325,8 @@ func (this NfsShare) Rm(path string) error { // PR aren't handled by vmware, we did come with the implementation as // of RFC1813 in: https://www.rfc-editor.org/rfc/rfc1813#section-3.3.14 func (this NfsShare) Mv(from string, to string) error { - defer this.Close() - + this.mu.Lock() + defer this.mu.Unlock() f, fName := filepath.Split(this.nfsPath(from)) _, fh, err := this.v.Lookup(f) if err != nil { @@ -314,7 +377,8 @@ func (this NfsShare) Touch(path string) error { } func (this NfsShare) Save(path string, file io.Reader) error { - defer this.Close() + this.mu.Lock() + defer this.mu.Unlock() w, err := this.v.OpenFile(path, 0644) if err != nil { return err