Skip to content

Commit

Permalink
meta: track atime for ReadLink as well (#3591)
Browse files Browse the repository at this point in the history
  • Loading branch information
SandyXSD authored May 12, 2023
1 parent 539910b commit d7260e0
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 228 deletions.
9 changes: 3 additions & 6 deletions pkg/fuse/fuse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import (
)

func format(url string) {
m := meta.NewClient(url, &meta.Config{})
m := meta.NewClient(url, nil)
format := &meta.Format{
Name: "test",
UUID: uuid.New().String(),
Expand All @@ -63,11 +63,8 @@ func mount(url, mp string) {
log.Fatalf("create %s: %s", mp, err)
}

metaConf := &meta.Config{
Retries: 10,
Strict: true,
MountPoint: mp,
}
metaConf := meta.DefaultConf()
metaConf.MountPoint = mp
m := meta.NewClient(url, metaConf)
format, err := m.Load(true)
if err != nil {
Expand Down
31 changes: 25 additions & 6 deletions pkg/meta/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package meta

import (
"encoding/binary"
"encoding/json"
"fmt"
"os"
Expand Down Expand Up @@ -89,7 +90,7 @@ type engine interface {
doLink(ctx Context, inode, parent Ino, name string, attr *Attr) syscall.Errno
doUnlink(ctx Context, parent Ino, name string, attr *Attr, skipCheckTrash ...bool) syscall.Errno
doRmdir(ctx Context, parent Ino, name string, inode *Ino, skipCheckTrash ...bool) syscall.Errno
doReadlink(ctx Context, inode Ino) ([]byte, error)
doReadlink(ctx Context, inode Ino, noatime bool) (int64, []byte, error)
doReaddir(ctx Context, inode Ino, plus uint8, entries *[]*Entry, limit int) syscall.Errno
doRename(ctx Context, parentSrc Ino, nameSrc string, parentDst Ino, nameDst string, flags uint32, inode, tinode *Ino, attr, tattr *Attr) syscall.Errno
doSetXattr(ctx Context, inode Ino, name string, value []byte, flags uint32) syscall.Errno
Expand Down Expand Up @@ -1320,20 +1321,38 @@ func (m *baseMeta) Link(ctx Context, inode, parent Ino, name string, attr *Attr)
}

func (m *baseMeta) ReadLink(ctx Context, inode Ino, path *[]byte) syscall.Errno {
noatime := m.conf.AtimeMode == NoAtime || m.conf.ReadOnly
if target, ok := m.symlinks.Load(inode); ok {
*path = target.([]byte)
return 0
if noatime {
*path = target.([]byte)
return 0
} else {
buf := target.([]byte)
// ctime and mtime are ignored since symlink can't be modified
attr := &Attr{Atime: int64(binary.BigEndian.Uint64(buf[:8]))}
if !m.atimeNeedsUpdate(attr, time.Now()) {
*path = buf[8:]
return 0
}
}
}
defer m.timeit("ReadLink", time.Now())
target, err := m.en.doReadlink(ctx, inode)
atime, target, err := m.en.doReadlink(ctx, inode, noatime)
if err != nil {
return errno(err)
}
if len(target) == 0 {
return syscall.ENOENT
}
*path = target
m.symlinks.Store(inode, target)
if noatime {
m.symlinks.Store(inode, target)
} else {
buf := make([]byte, 8+len(target))
binary.BigEndian.PutUint64(buf[:8], uint64(atime))
copy(buf[8:], target)
m.symlinks.Store(inode, buf)
}
return 0
}

Expand Down Expand Up @@ -1497,7 +1516,7 @@ func (m *baseMeta) Rename(ctx Context, parentSrc Ino, nameSrc string, parentDst

// caller makes sure inode is not special inode.
func (m *baseMeta) touchAtime(ctx Context, inode Ino, attr *Attr) {
if (m.conf.AtimeMode == NoAtime) || m.conf.ReadOnly {
if m.conf.AtimeMode == NoAtime || m.conf.ReadOnly {
return
}

Expand Down
118 changes: 118 additions & 0 deletions pkg/meta/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ func testMeta(t *testing.T, m Meta) {
testConcurrentDir(t, m)
testAttrFlags(t, m)
testQuota(t, m)
testAtime(t, m)
base := m.getBase()
base.conf.OpenCache = time.Second
base.of.expire = time.Second
Expand Down Expand Up @@ -2517,3 +2518,120 @@ func testQuota(t *testing.T, m Meta) {
t.Fatalf("Create quota/d22/f3: %s", st)
}
}

func testAtime(t *testing.T, m Meta) {
ctx := Background
var inode, parent Ino
var attr Attr
if st := m.Mkdir(ctx, RootInode, "atime", 0755, 0, 0, &parent, &attr); st != 0 {
t.Fatalf("Mkdir atime: %s", st)
}

// open, read, read atime < mtime, read recent, readdir, readlink
testFn := func(name string) (ret [6]bool) {
fname := "f-" + name
if st := m.Create(ctx, parent, fname, 0644, 0, 0, &inode, &attr); st != 0 {
t.Fatalf("Create atime/%s: %s", fname, st)
}
// atime < ctime
attr.Atime, attr.Atimensec = 1234, 5678
if st := m.SetAttr(ctx, inode, SetAttrAtime, 0, &attr); st != 0 {
t.Fatalf("Setattr atime/%s: %s", fname, st)
}
if st := m.Open(ctx, inode, 0, &attr); st != 0 {
t.Fatalf("Open atime/%s: %s", fname, st)
}
defer m.Close(ctx, inode)
ret[0] = attr.Atime != 1234

attr.Atime, attr.Atimensec = 1234, 5678
if st := m.SetAttr(ctx, inode, SetAttrAtime, 0, &attr); st != 0 {
t.Fatalf("Setattr atime/%s: %s", fname, st)
}
var slices []Slice
if st := m.Read(ctx, inode, 0, &slices); st != 0 {
t.Fatalf("Read atime/%s: %s", fname, st)
}
if st := m.GetAttr(ctx, inode, &attr); st != 0 {
t.Fatalf("Getattr after read atime/%s: %s", fname, st)
}
ret[1] = attr.Atime != 1234

// atime < mtime
now := time.Now()
attr.Atime = now.Unix() - 2
attr.Mtime = now.Unix()
if st := m.SetAttr(ctx, inode, SetAttrAtime|SetAttrMtime, 0, &attr); st != 0 {
t.Fatalf("Setattr atime/%s: %s", fname, st)
}
if st := m.Read(ctx, inode, 0, &slices); st != 0 {
t.Fatalf("Read atime/%s: %s", fname, st)
}
if st := m.GetAttr(ctx, inode, &attr); st != 0 {
t.Fatalf("Getattr after read atime/%s: %s", fname, st)
}
ret[2] = attr.Atime >= now.Unix()

// atime = ctime = mtime, atime = now
if st := m.SetAttr(ctx, inode, SetAttrAtimeNow|SetAttrMtimeNow, 0, &attr); st != 0 {
t.Fatalf("Setattr atime/%s: %s", fname, st)
}
time.Sleep(time.Second * 2)
now = time.Now()
if st := m.Read(ctx, inode, 0, &slices); st != 0 {
t.Fatalf("Read atime/%s: %s", fname, st)
}
if st := m.GetAttr(ctx, inode, &attr); st != 0 {
t.Fatalf("Getattr after read atime/%s: %s", fname, st)
}
ret[3] = attr.Atime >= now.Unix()

// readdir
fname = "d-" + name
if st := m.Mkdir(ctx, parent, fname, 0755, 0, 0, &inode, &attr); st != 0 {
t.Fatalf("Mkdir atime/%s: %s", fname, st)
}
attr.Atime, attr.Atimensec = 1234, 5678
if st := m.SetAttr(ctx, inode, SetAttrAtime, 0, &attr); st != 0 {
t.Fatalf("Setattr atime/%s: %s", fname, st)
}
var entries []*Entry
if st := m.Readdir(ctx, inode, 0, &entries); st != 0 {
t.Fatalf("Readdir atime/%s: %s", fname, st)
}
if st := m.GetAttr(ctx, inode, &attr); st != 0 {
t.Fatalf("Getattr after readdir atime/%s: %s", fname, st)
}
ret[4] = attr.Atime != 1234

// readlink
fname = "l-" + name
if st := m.Symlink(ctx, parent, fname, "f-"+name, &inode, &attr); st != 0 {
t.Fatalf("Symlink atime/%s: %s", fname, st)
}
attr.Atime, attr.Atimensec = 1234, 5678
if st := m.SetAttr(ctx, inode, SetAttrAtime, 0, &attr); st != 0 {
t.Fatalf("Setattr atime/%s: %s", fname, st)
}
var target []byte
if st := m.ReadLink(ctx, inode, &target); st != 0 {
t.Fatalf("Readlink atime/%s: %s", fname, st)
}
if st := m.GetAttr(ctx, inode, &attr); st != 0 {
t.Fatalf("Getattr after readlink atime/%s: %s", fname, st)
}
ret[5] = attr.Atime != 1234
return
}

for name, exp := range map[string][6]bool{
RelAtime: {true, true, true, false, true, true},
StrictAtime: {true, true, true, true, true, true},
NoAtime: {false, false, false, false, false, false},
} {
m.getBase().conf.AtimeMode = name
if ret := testFn(name); ret != exp {
t.Fatalf("Test %s: expected %v, got %v", name, exp, ret)
}
}
}
2 changes: 1 addition & 1 deletion pkg/meta/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type Config struct {
}

func DefaultConf() *Config {
return &Config{Strict: true, Retries: 10, MaxDeletes: 2, Heartbeat: 12 * time.Second}
return &Config{Strict: true, Retries: 10, MaxDeletes: 2, Heartbeat: 12 * time.Second, AtimeMode: NoAtime}
}

func (c *Config) SelfCheck() {
Expand Down
29 changes: 27 additions & 2 deletions pkg/meta/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -1169,8 +1169,33 @@ func (m *redisMeta) SetAttr(ctx Context, inode Ino, set uint16, sugidclearmode u
}, m.inodeKey(inode)))
}

func (m *redisMeta) doReadlink(ctx Context, inode Ino) ([]byte, error) {
return m.rdb.Get(ctx, m.symKey(inode)).Bytes()
func (m *redisMeta) doReadlink(ctx Context, inode Ino, noatime bool) (atime int64, target []byte, err error) {
if noatime {
target, err = m.rdb.Get(ctx, m.symKey(inode)).Bytes()
return
}

attr := &Attr{}
now := time.Now()
err = m.txn(ctx, func(tx *redis.Tx) error {
rs, e := tx.MGet(ctx, m.inodeKey(inode), m.symKey(inode)).Result()
if e != nil {
return e
}
if rs[0] == nil || rs[1] == nil {
return redis.Nil
}
m.parseAttr([]byte(rs[0].(string)), attr)
target = []byte(rs[1].(string))
if !m.atimeNeedsUpdate(attr, now) {
return nil
}
attr.Atime = now.Unix()
attr.Atimensec = uint32(now.Nanosecond())
return tx.Set(ctx, m.inodeKey(inode), m.marshal(attr), 0).Err()
}, m.inodeKey(inode))
atime = attr.Atime
return
}

func (m *redisMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode, cumask uint16, rdev uint32, path string, inode *Ino, attr *Attr) syscall.Errno {
Expand Down
48 changes: 41 additions & 7 deletions pkg/meta/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -1130,15 +1130,49 @@ func (m *dbMeta) Fallocate(ctx Context, inode Ino, mode uint8, off uint64, size
return errno(err)
}

func (m *dbMeta) doReadlink(ctx Context, inode Ino) (target []byte, err error) {
err = m.roTxn(func(s *xorm.Session) error {
var l = symlink{Inode: inode}
ok, err := s.Get(&l)
if err == nil && ok {
target = []byte(l.Target)
func (m *dbMeta) doReadlink(ctx Context, inode Ino, noatime bool) (atime int64, target []byte, err error) {
if noatime {
err = m.roTxn(func(s *xorm.Session) error {
var l = symlink{Inode: inode}
ok, err := s.Get(&l)
if err == nil && ok {
target = l.Target
}
return err
})
return
}

attr := &Attr{}
now := time.Now()
err = m.txn(func(s *xorm.Session) error {
nodeAttr := node{Inode: inode}
ok, e := s.ForUpdate().Get(&nodeAttr)
if e != nil {
return e
}
return err
if !ok {
return syscall.ENOENT
}
l := symlink{Inode: inode}
ok, e = s.Get(&l)
if e != nil {
return e
}
if !ok {
return syscall.ENOENT
}
m.parseAttr(&nodeAttr, attr)
target = l.Target
if !m.atimeNeedsUpdate(attr, now) {
return nil
}
nodeAttr.Atime = now.Unix()*1e6 + int64(now.Nanosecond())/1e3
attr.Atime = now.Unix()
_, e = s.Cols("atime").Update(&nodeAttr, &node{Inode: inode})
return e
})
atime = attr.Atime
return
}

Expand Down
27 changes: 25 additions & 2 deletions pkg/meta/tkv.go
Original file line number Diff line number Diff line change
Expand Up @@ -1077,8 +1077,31 @@ func (m *kvMeta) Fallocate(ctx Context, inode Ino, mode uint8, off uint64, size
return errno(err)
}

func (m *kvMeta) doReadlink(ctx Context, inode Ino) ([]byte, error) {
return m.get(m.symKey(inode))
func (m *kvMeta) doReadlink(ctx Context, inode Ino, noatime bool) (atime int64, target []byte, err error) {
if noatime {
target, err = m.get(m.symKey(inode))
return
}

attr := &Attr{}
now := time.Now()
err = m.txn(func(tx *kvTxn) error {
rs := tx.gets(m.inodeKey(inode), m.symKey(inode))
if rs[0] == nil || rs[1] == nil {
return syscall.ENOENT
}
m.parseAttr(rs[0], attr)
target = rs[1]
if !m.atimeNeedsUpdate(attr, now) {
return nil
}
attr.Atime = now.Unix()
attr.Atimensec = uint32(now.Nanosecond())
tx.set(m.inodeKey(inode), m.marshal(attr))
return nil
})
atime = attr.Atime
return
}

func (m *kvMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode, cumask uint16, rdev uint32, path string, inode *Ino, attr *Attr) syscall.Errno {
Expand Down
26 changes: 6 additions & 20 deletions pkg/meta/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -560,30 +560,16 @@ func (m *baseMeta) getTreeSummary(ctx Context, tree *TreeSummary, depth, topN ui
}

func (m *baseMeta) atimeNeedsUpdate(attr *Attr, now time.Time) bool {
if m.conf.AtimeMode == RelAtime && relatimeNeedUpdate(attr, now) {
return true
}

atime := time.Unix(attr.Atime, int64(attr.Atimensec))
// update atime only for > 1 second accesses
return (m.conf.AtimeMode == StrictAtime) && (now.Sub(atime) > time.Second)
return m.conf.AtimeMode != NoAtime && relatimeNeedUpdate(attr, now) ||
// update atime only for > 1 second accesses
m.conf.AtimeMode == StrictAtime && now.Sub(time.Unix(attr.Atime, int64(attr.Atimensec))) > time.Second
}

// With relative atime, only update atime if the previous atime is earlier than either the ctime or
// mtime or if at least a day has passed since the last atime update.
func relatimeNeedUpdate(attr *Attr, now time.Time) bool {
atime := time.Unix(attr.Atime, int64(attr.Atimensec))

// Is mtime younger than atime? If yes, update atime
if time.Unix(attr.Mtime, int64(attr.Mtimensec)).After(atime) {
return true
}

// Is ctime younger than atime? If yes, update atime
if time.Unix(attr.Ctime, int64(attr.Ctimensec)).After(atime) {
return true
}

// Is the previous atime value older than a day? If yes, update atime
return now.Sub(atime) > 24*time.Hour
mtime := time.Unix(attr.Mtime, int64(attr.Mtimensec))
ctime := time.Unix(attr.Ctime, int64(attr.Ctimensec))
return mtime.After(atime) || ctime.After(atime) || now.Sub(atime) > 24*time.Hour
}
Loading

0 comments on commit d7260e0

Please sign in to comment.