windows: add attr, entry and dir-entry cache; (#6053)

This commit is contained in:
ethan
2025-05-07 21:41:29 +08:00
committed by GitHub
parent 1c173f70be
commit 6fa55a567e
5 changed files with 197 additions and 54 deletions

View File

@@ -132,7 +132,7 @@ jobs:
- name: Juicefs Mount
run: |
$env:PATH+=";C:\Program Files (x86)\WinFsp\bin"
./juicefs.exe mount -d redis://127.0.0.1:6379/1 z:
./juicefs.exe mount -d redis://127.0.0.1:6379/1 z: --fuse-trace-log c:/fuse.log
- name: Run Winfsp Tests
run: |

View File

@@ -428,8 +428,14 @@ func bench(ctx *cli.Context) error {
progress.Done()
/* --- Clean-up --- */
if err := exec.Command("rm", "-rf", bm.tmpdir).Run(); err != nil {
logger.Warnf("Failed to cleanup %s: %s", bm.tmpdir, err)
if runtime.GOOS == "windows" {
if err := exec.Command("cmd", "/C", "rd", "/s", "/q", bm.tmpdir).Run(); err != nil {
logger.Warnf("Failed to cleanup %s: %s", bm.tmpdir, err)
}
} else {
if err := exec.Command("rm", "-rf", bm.tmpdir).Run(); err != nil {
logger.Warnf("Failed to cleanup %s: %s", bm.tmpdir, err)
}
}
/* --- Report --- */

View File

@@ -104,9 +104,10 @@ func getDaemonStage() int {
func mountMain(v *vfs.VFS, c *cli.Context) {
v.Conf.AccessLog = c.String("access-log")
v.Conf.AttrTimeout = utils.Duration(c.String("attr-cache"))
v.Conf.EntryTimeout = utils.Duration(c.String("entry-cache"))
v.Conf.DirEntryTimeout = utils.Duration(c.String("dir-entry-cache"))
fileCacheTimeout := utils.Duration(c.String("entry-cache"))
dirCacheTimeout := utils.Duration(c.String("dir-entry-cache"))
delayCloseTime := utils.Duration(c.String("delay-close"))
traceLog := c.String("fuse-trace-log")
@@ -114,7 +115,7 @@ func mountMain(v *vfs.VFS, c *cli.Context) {
winfsp.SetTraceOutput(traceLog)
}
winfsp.Serve(v, c.String("o"), fileCacheTimeout.Seconds(), dirCacheTimeout.Seconds(),
winfsp.Serve(v, c.String("o"),
c.Bool("as-root"), int(delayCloseTime.Seconds()), c.Bool("show-dot-files"),
c.Int("winfsp-threads"), c.Bool("case-sensitive"), c.Bool("report-case"))
}

View File

@@ -113,6 +113,8 @@ func (fs *FileStat) Gid() int { return int(fs.attr.Gid) }
func (fs *FileStat) Atime() int64 { return fs.attr.Atime*1000 + int64(fs.attr.Atimensec/1e6) }
func (fs *FileStat) Mtime() int64 { return fs.attr.Mtime*1000 + int64(fs.attr.Mtimensec/1e6) }
func (fs *FileStat) Attr() *Attr { return fs.attr }
func AttrToFileInfo(inode Ino, attr *Attr) *FileStat {
return &FileStat{inode: inode, attr: attr}
}
@@ -252,7 +254,7 @@ func (fs *FileSystem) cleanupCache() {
}
}
func (fs *FileSystem) invalidateEntry(parent Ino, name string) {
func (fs *FileSystem) InvalidateEntry(parent Ino, name string) {
fs.cacheM.Lock()
defer fs.cacheM.Unlock()
es, ok := fs.entries[parent]
@@ -264,7 +266,7 @@ func (fs *FileSystem) invalidateEntry(parent Ino, name string) {
}
}
func (fs *FileSystem) invalidateAttr(ino Ino) {
func (fs *FileSystem) InvalidateAttr(ino Ino) {
fs.cacheM.Lock()
defer fs.cacheM.Unlock()
delete(fs.attrs, ino)
@@ -453,7 +455,7 @@ func (fs *FileSystem) Mkdir(ctx meta.Context, p string, mode uint16, umask uint1
if fs.conf.DirEntryTimeout > 0 {
parent := parentDir(p)
if fi, err := fs.resolve(ctx, parentDir(parent), true); err == 0 {
fs.invalidateEntry(fi.inode, path.Base(parent))
fs.InvalidateEntry(fi.inode, path.Base(parent))
}
}
if fi2, e := fs.resolve(ctx, parentDir(p), true); e != 0 {
@@ -462,7 +464,7 @@ func (fs *FileSystem) Mkdir(ctx meta.Context, p string, mode uint16, umask uint1
err = fs.m.Mkdir(ctx, fi2.inode, path.Base(p), mode, umask, 0, &inode, nil)
}
}
fs.invalidateEntry(fi.inode, path.Base(p))
fs.InvalidateEntry(fi.inode, path.Base(p))
return
}
@@ -516,7 +518,7 @@ func (fs *FileSystem) Delete0(ctx meta.Context, p string, callByUnlink bool) (er
} else {
err = fs.m.Unlink(ctx, parent.inode, path.Base(p))
}
fs.invalidateEntry(parent.inode, path.Base(p))
fs.InvalidateEntry(parent.inode, path.Base(p))
return
}
@@ -529,7 +531,7 @@ func (fs *FileSystem) Rmdir(ctx meta.Context, p string) (err syscall.Errno) {
return
}
err = fs.m.Rmdir(ctx, parent.inode, path.Base(p))
fs.invalidateEntry(parent.inode, path.Base(p))
fs.InvalidateEntry(parent.inode, path.Base(p))
return
}
@@ -542,7 +544,7 @@ func (fs *FileSystem) Rmr(ctx meta.Context, p string, skipTrash bool, numthreads
return
}
err = fs.m.Remove(ctx, parent.inode, path.Base(p), skipTrash, numthreads, nil)
fs.invalidateEntry(parent.inode, path.Base(p))
fs.InvalidateEntry(parent.inode, path.Base(p))
return
}
@@ -601,8 +603,8 @@ func (fs *FileSystem) Rename(ctx meta.Context, oldpath string, newpath string, f
return err0
}
err = fs.m.Rename(ctx, oldfi.inode, path.Base(oldpath), newfi.inode, path.Base(newpath), flags, nil, nil)
fs.invalidateEntry(oldfi.inode, path.Base(oldpath))
fs.invalidateEntry(newfi.inode, path.Base(newpath))
fs.InvalidateEntry(oldfi.inode, path.Base(oldpath))
fs.InvalidateEntry(newfi.inode, path.Base(newpath))
return
}
@@ -620,7 +622,7 @@ func (fs *FileSystem) Link(ctx meta.Context, src string, dst string) (err syscal
return
}
err = fs.m.Link(ctx, fi.inode, pi.inode, path.Base(dst), nil)
fs.invalidateEntry(pi.inode, path.Base(dst))
fs.InvalidateEntry(pi.inode, path.Base(dst))
return
}
@@ -636,7 +638,7 @@ func (fs *FileSystem) Symlink(ctx meta.Context, target string, link string) (err
return
}
err = fs.m.Symlink(ctx, fi.inode, path.Base(link), target, nil, nil)
fs.invalidateEntry(fi.inode, path.Base(link))
fs.InvalidateEntry(fi.inode, path.Base(link))
return
}
@@ -949,7 +951,7 @@ func (fs *FileSystem) Create(ctx meta.Context, p string, mode uint16, umask uint
if fs.conf.DirEntryTimeout > 0 {
parent := parentDir(p)
if fi, err := fs.resolve(ctx, parentDir(parent), true); err == 0 {
fs.invalidateEntry(fi.inode, path.Base(parent))
fs.InvalidateEntry(fi.inode, path.Base(parent))
}
}
if fi2, e := fs.resolve(ctx, parentDir(p), true); e != 0 {
@@ -968,7 +970,7 @@ func (fs *FileSystem) Create(ctx meta.Context, p string, mode uint16, umask uint
f.info = fi
f.fs = fs
}
fs.invalidateEntry(fi.inode, path.Base(p))
fs.InvalidateEntry(fi.inode, path.Base(p))
return
}
@@ -1089,7 +1091,7 @@ func (f *File) Chmod(ctx meta.Context, mode uint16) (err syscall.Errno) {
defer func() { f.fs.log(l, "Chmod (%s,%o): %s", f.path, mode, errstr(err)) }()
var attr = Attr{Mode: mode}
err = f.fs.m.SetAttr(ctx, f.inode, meta.SetAttrMode, 0, &attr)
f.fs.invalidateAttr(f.inode)
f.fs.InvalidateAttr(f.inode)
return
}
@@ -1106,7 +1108,7 @@ func (f *File) Chown(ctx meta.Context, uid uint32, gid uint32) (err syscall.Errn
}
var attr = Attr{Uid: uid, Gid: gid}
err = f.fs.m.SetAttr(ctx, f.inode, flag, 0, &attr)
f.fs.invalidateAttr(f.inode)
f.fs.InvalidateAttr(f.inode)
return
}
@@ -1130,7 +1132,7 @@ func (f *File) Utime(ctx meta.Context, atime, mtime int64) (err syscall.Errno) {
attr.Mtime = mtime / 1000
attr.Mtimensec = uint32(mtime%1000) * 1e6
err = f.fs.m.SetAttr(ctx, f.inode, flag, 0, &attr)
f.fs.invalidateAttr(f.inode)
f.fs.InvalidateAttr(f.inode)
return
}
@@ -1156,7 +1158,7 @@ func (f *File) Utime2(ctx meta.Context, atimeSec, atimeNSec, mtimeSec, mtimeNsec
attr.Mtime = mtimeSec
attr.Mtimensec = uint32(mtimeNsec)
err = f.fs.m.SetAttr(ctx, f.inode, flag, 0, &attr)
f.fs.invalidateAttr(f.inode)
f.fs.InvalidateAttr(f.inode)
return
}
@@ -1289,7 +1291,7 @@ func (f *File) Truncate(ctx meta.Context, length uint64) (err syscall.Errno) {
f.fs.writer.Truncate(f.inode, length)
f.fs.reader.Truncate(f.inode, length)
f.info.attr.Length = length
f.fs.invalidateAttr(f.inode)
f.fs.InvalidateAttr(f.inode)
}
return
}
@@ -1304,7 +1306,7 @@ func (f *File) Flush(ctx meta.Context) (err syscall.Errno) {
l := vfs.NewLogContext(ctx)
defer func() { f.fs.log(l, "Flush (%s): %s", f.path, errstr(err)) }()
err = f.wdata.Flush(ctx)
f.fs.invalidateAttr(f.inode)
f.fs.InvalidateAttr(f.inode)
return
}
@@ -1318,7 +1320,7 @@ func (f *File) Fsync(ctx meta.Context) (err syscall.Errno) {
l := vfs.NewLogContext(ctx)
defer func() { f.fs.log(l, "Fsync (%s): %s", f.path, errstr(err)) }()
err = f.wdata.Flush(ctx)
f.fs.invalidateAttr(f.inode)
f.fs.InvalidateAttr(f.inode)
return
}
@@ -1338,7 +1340,7 @@ func (f *File) Close(ctx meta.Context) (err syscall.Errno) {
}
if f.wdata != nil {
err = f.wdata.Close(meta.Background())
f.fs.invalidateAttr(f.inode)
f.fs.InvalidateAttr(f.inode)
f.wdata = nil
}
_ = f.fs.m.Close(ctx, f.inode)

View File

@@ -40,8 +40,16 @@ import (
var logger = utils.GetLogger("juicefs")
const invalidFileHandle = uint64(0xffffffffffffffff)
type Ino = meta.Ino
type handleInfo struct {
ino meta.Ino
cacheAttr *meta.Attr
attrExpiredAt time.Time
}
func trace(vals ...interface{}) func(vals ...interface{}) {
uid, gid, pid := fuse.Getcontext()
return Trace(1, fmt.Sprintf("[uid=%v,gid=%v,pid=%d]", uid, gid, pid), vals...)
@@ -49,24 +57,28 @@ func trace(vals ...interface{}) func(vals ...interface{}) {
type juice struct {
fuse.FileSystemBase
sync.Mutex
conf *vfs.Config
vfs *vfs.VFS
fs *fs.FileSystem
host *fuse.FileSystemHost
handlers map[uint64]meta.Ino
badfd map[uint64]uint64
sync.RWMutex
conf *vfs.Config
vfs *vfs.VFS
fs *fs.FileSystem
host *fuse.FileSystemHost
handlers map[uint64]handleInfo
badfd map[uint64]uint64
inoHandleMap map[meta.Ino][]uint64
asRoot bool
delayClose int
enabledGetPath bool
disableSymlink bool
attrCacheTimeout time.Duration
}
// Init is called when the file system is created.
func (j *juice) Init() {
j.handlers = make(map[uint64]meta.Ino)
j.handlers = make(map[uint64]handleInfo)
j.badfd = make(map[uint64]uint64)
j.inoHandleMap = make(map[meta.Ino][]uint64)
}
func (j *juice) newContext() vfs.LogContext {
@@ -74,10 +86,10 @@ func (j *juice) newContext() vfs.LogContext {
return vfs.NewLogContext(meta.Background())
}
uid, gid, pid := fuse.Getcontext()
if uid == 0xffffffff {
if uid == 0xffffffff || uid == 18 {
uid = 0
}
if gid == 0xffffffff {
if gid == 0xffffffff || gid == 18 {
gid = 0
}
if pid == -1 {
@@ -179,6 +191,9 @@ func (j *juice) Mknod(p string, mode uint32, dev uint64) (e int) {
}
_, errno := j.vfs.Mknod(ctx, parent.Inode(), path.Base(p), uint16(mode), 0, uint32(dev))
e = errorconv(errno)
if e == 0 {
j.fs.InvalidateEntry(parent.Inode(), path.Base(p))
}
return
}
@@ -261,6 +276,9 @@ func (j *juice) Chmod(path string, mode uint32) (e int) {
return
}
e = errorconv(f.Chmod(ctx, uint16(mode)))
if e == 0 {
j.invalidateAttrCache(f.Inode())
}
return
}
@@ -297,6 +315,9 @@ func (j *juice) Utimens(path string, tmsp []fuse.Timespec) (e int) {
e = errorconv(err)
} else {
e = errorconv(f.Utime2(ctx, tmsp[0].Sec, tmsp[0].Nsec, tmsp[1].Sec, tmsp[1].Nsec))
if e == 0 {
j.invalidateAttrCache(f.Inode())
}
}
return
}
@@ -315,10 +336,18 @@ func (j *juice) Create(p string, flags int, mode uint32) (e int, fh uint64) {
entry, fh, errno := j.vfs.Create(ctx, parent.Inode(), path.Base(p), uint16(mode), 0, uint32(fuseFlagToSyscall(flags)))
if errno == 0 {
j.Lock()
j.handlers[fh] = entry.Inode
j.handlers[fh] = handleInfo{
ino: entry.Inode,
cacheAttr: entry.Attr,
attrExpiredAt: time.Now().Add(j.conf.AttrTimeout),
}
j.inoHandleMap[entry.Inode] = append(j.inoHandleMap[entry.Inode], fh)
j.Unlock()
}
e = errorconv(errno)
if e == 0 {
j.fs.InvalidateEntry(parent.Inode(), path.Base(p))
}
return
}
@@ -336,7 +365,7 @@ func (j *juice) Open(path string, flags int) (e int, fh uint64) {
// The flags are a combination of the fuse.O_* constants.
func (j *juice) OpenEx(p string, fi *fuse.FileInfo_t) (e int) {
ctx := j.newContext()
defer trace(p, fi.Flags)(&e)
defer trace(p, fi.Flags)(&e, &fi.Fh)
ino := meta.Ino(0)
if strings.HasSuffix(p, "/.control") {
ino, _ = vfs.GetInternalNodeByName(".control")
@@ -368,7 +397,12 @@ func (j *juice) OpenEx(p string, fi *fuse.FileInfo_t) (e int) {
fi.KeepCache = entry.Attr.KeepCache
}
j.Lock()
j.handlers[fh] = ino
j.handlers[fh] = handleInfo{
ino: ino,
cacheAttr: entry.Attr,
attrExpiredAt: time.Now().Add(j.conf.AttrTimeout),
}
j.inoHandleMap[ino] = append(j.inoHandleMap[ino], fh)
j.Unlock()
}
e = errorconv(errno)
@@ -429,19 +463,20 @@ func attrToStat(inode Ino, attr *meta.Attr, stat *fuse.Stat_t) {
}
func (j *juice) h2i(fh *uint64) meta.Ino {
defer j.Unlock()
j.Lock()
ino := j.handlers[*fh]
if ino == 0 {
defer j.RUnlock()
j.RLock()
entry := j.handlers[*fh]
if entry.ino == 0 {
newfh := j.badfd[*fh]
if newfh != 0 {
ino = j.handlers[newfh]
if ino > 0 {
entry = j.handlers[newfh]
if entry.ino > 0 {
*fh = newfh
}
}
}
return ino
return entry.ino
}
func (j *juice) reopen(p string, fh *uint64) meta.Ino {
@@ -453,7 +488,7 @@ func (j *juice) reopen(p string, fh *uint64) meta.Ino {
defer j.Unlock()
j.badfd[*fh] = newfh
*fh = newfh
return j.handlers[newfh]
return j.handlers[newfh].ino
}
// Getattr gets file attributes.
@@ -481,11 +516,77 @@ func (j *juice) getAttrForSpFile(ctx vfs.LogContext, p string, stat *fuse.Stat_t
return
}
func (j *juice) invalidateAttrCache(ino meta.Ino) {
if j.attrCacheTimeout == 0 || ino == 0 {
return
}
j.fs.InvalidateAttr(ino) // invalidate the attrcache in fs layer
j.Lock()
defer j.Unlock()
handlers := j.inoHandleMap[ino]
for _, fh := range handlers {
if cache, ok := j.handlers[fh]; ok {
cache.cacheAttr = nil
cache.attrExpiredAt = time.Time{}
j.handlers[fh] = cache
}
}
}
func (j *juice) getAttrFromCache(fh uint64) (entry *meta.Entry) {
if j.attrCacheTimeout == 0 || fh == invalidFileHandle {
return nil
}
j.RLock()
defer j.RUnlock()
if cache, ok := j.handlers[fh]; ok && cache.cacheAttr != nil {
if time.Now().Before(cache.attrExpiredAt) {
entry = &meta.Entry{
Inode: cache.ino,
Attr: cache.cacheAttr,
}
return entry
}
}
return nil
}
func (j *juice) setAttrCache(fh uint64, attr *meta.Attr) {
if j.attrCacheTimeout == 0 || fh == invalidFileHandle {
return
}
j.Lock()
defer j.Unlock()
if cache, ok := j.handlers[fh]; ok {
cache.cacheAttr = attr
cache.attrExpiredAt = time.Now().Add(j.attrCacheTimeout)
j.handlers[fh] = cache
}
}
func (j *juice) getAttr(ctx vfs.Context, fh uint64, ino Ino, opened uint8) (entry *meta.Entry, err syscall.Errno) {
if entry := j.getAttrFromCache(fh); entry != nil {
return entry, 0
}
if entry, err = j.vfs.GetAttr(ctx, ino, opened); err != 0 {
return nil, err
}
j.setAttrCache(fh, entry.Attr)
return entry, 0
}
// Getattr gets file attributes.
func (j *juice) Getattr(p string, stat *fuse.Stat_t, fh uint64) (e int) {
ctx := j.newContext()
defer trace(p, fh)(stat, &e)
ino := j.h2i(&fh)
if ino == 0 {
// special case for .control file
if strings.HasSuffix(p, "/.control") {
@@ -505,8 +606,15 @@ func (j *juice) Getattr(p string, stat *fuse.Stat_t, fh uint64) (e int) {
return
}
ino = fi.Inode()
entry := fi.Attr()
if entry != nil {
j.vfs.UpdateLength(ino, entry)
attrToStat(ino, entry, stat)
return
}
}
entry, errrno := j.vfs.GetAttr(ctx, ino, 0)
entry, errrno := j.getAttr(ctx, fh, ino, 0)
if errrno != 0 {
e = errorconv(errrno)
return
@@ -526,6 +634,9 @@ func (j *juice) Truncate(path string, size int64, fh uint64) (e int) {
return
}
e = errorconv(j.vfs.Truncate(ctx, ino, size, 0, nil))
if e == 0 {
j.invalidateAttrCache(ino)
}
return
}
@@ -569,6 +680,7 @@ func (j *juice) Write(path string, buff []byte, off int64, fh uint64) (e int) {
} else {
e = len(buff)
}
return
}
@@ -585,6 +697,19 @@ func (j *juice) Flush(path string, fh uint64) (e int) {
return
}
func (j *juice) cleanInoHandlerMap(ino meta.Ino, fh uint64) {
handles := j.inoHandleMap[ino]
for i, handle := range handles {
if handle == fh {
j.inoHandleMap[ino] = append(handles[:i], handles[i+1:]...)
break
}
}
if len(j.inoHandleMap[ino]) == 0 {
delete(j.inoHandleMap, ino)
}
}
// Release closes an open file.
func (j *juice) Release(path string, fh uint64) int {
defer trace(path, fh)()
@@ -598,8 +723,10 @@ func (j *juice) Release(path string, fh uint64) int {
time.Sleep(time.Second * time.Duration(j.delayClose))
j.Lock()
delete(j.handlers, fh)
j.cleanInoHandlerMap(ino, fh)
if orig != fh {
delete(j.badfd, orig)
j.cleanInoHandlerMap(ino, orig)
}
j.Unlock()
j.vfs.Release(j.newContext(), ino, fh)
@@ -632,7 +759,11 @@ func (j *juice) Opendir(path string) (e int, fh uint64) {
fh, errno := j.vfs.Opendir(ctx, f.Inode(), 0)
if errno == 0 {
j.Lock()
j.handlers[fh] = f.Inode()
j.handlers[fh] = handleInfo{
ino: f.Inode(),
}
j.inoHandleMap[f.Inode()] = append(j.inoHandleMap[f.Inode()], fh)
j.Unlock()
}
e = errorconv(errno)
@@ -696,6 +827,7 @@ func (j *juice) Releasedir(path string, fh uint64) (e int) {
}
j.Lock()
delete(j.handlers, fh)
j.cleanInoHandlerMap(ino, fh)
j.Unlock()
e = -int(j.vfs.Releasedir(j.newContext(), ino, fh))
return
@@ -729,6 +861,8 @@ func (j *juice) Chflags(path string, flags uint32) (e int) {
err = j.vfs.ChFlags(ctx, ino, flagSet)
if err != 0 {
e = errorconv(err)
} else {
j.invalidateAttrCache(ino)
}
return
@@ -786,10 +920,10 @@ func (j *juice) Getpath(p string, fh uint64) (e int, ret string) {
return
}
func Serve(v *vfs.VFS, fuseOpt string, fileCacheTimeoutSec float64, dirCacheTimeoutSec float64,
asRoot bool, delayCloseSec int, showDotFiles bool, threadsCount int, caseSensitive bool, enabledGetPath bool) {
func Serve(v *vfs.VFS, fuseOpt string, asRoot bool, delayCloseSec int, showDotFiles bool, threadsCount int, caseSensitive bool, enabledGetPath bool) {
var jfs juice
conf := v.Conf
jfs.attrCacheTimeout = v.Conf.AttrTimeout
jfs.conf = conf
jfs.vfs = v
jfs.enabledGetPath = enabledGetPath
@@ -805,8 +939,8 @@ func Serve(v *vfs.VFS, fuseOpt string, fileCacheTimeoutSec float64, dirCacheTime
jfs.host = host
var options = "volname=" + conf.Format.Name
options += fmt.Sprintf(",ExactFileSystemName=JuiceFS,ThreadCount=%d", threadsCount)
options += fmt.Sprintf(",DirInfoTimeout=%d,VolumeInfoTimeout=1000,KeepFileCache", int(dirCacheTimeoutSec*1000))
options += fmt.Sprintf(",FileInfoTimeout=%d", int(fileCacheTimeoutSec*1000))
options += fmt.Sprintf(",DirInfoTimeout=%d,VolumeInfoTimeout=1000,KeepFileCache", int(conf.DirEntryTimeout.Seconds()*1000))
options += fmt.Sprintf(",FileInfoTimeout=%d", int(conf.EntryTimeout.Seconds()*1000))
options += ",VolumePrefix=/juicefs/" + conf.Format.Name
if asRoot {
options += ",uid=-1,gid=-1"