Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vfs: introduce --atime-mode option to control atime update behavior #3521

Merged
merged 7 commits into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ func clientFlags() []cli.Flag {
Name: "subdir",
Usage: "mount a sub-directory as root",
},
&cli.StringFlag{
Name: "atime-mode",
eryugey marked this conversation as resolved.
Show resolved Hide resolved
Value: "noatime",
Usage: "when to update atime, supported mode includes: noatime, relatime, strictatime",
},
}
}

Expand Down
7 changes: 7 additions & 0 deletions cmd/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,13 @@ func getMetaConf(c *cli.Context, mp string, readOnly bool) *meta.Config {
conf.Heartbeat = duration(c.String("heartbeat"))
conf.MountPoint = mp
conf.Subdir = c.String("subdir")

atimeMode := c.String("atime-mode")
if atimeMode != meta.RelAtime && atimeMode != meta.StrictAtime && atimeMode != meta.NoAtime {
logger.Warnf("unknown atime-mode \"%s\", changed to %s", atimeMode, meta.NoAtime)
eryugey marked this conversation as resolved.
Show resolved Hide resolved
atimeMode = meta.NoAtime
}
conf.AtimeMode = atimeMode
return conf
}

Expand Down
5 changes: 4 additions & 1 deletion docs/en/development/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,10 @@ type Attr struct {

There are a few fields that need clarification.

- Atime/Atimensec: set only when the file is created and when `SetAttr` is actively called, while accessing and modifying the file usually does not affect the Atime value
- Atime/Atimensec: currently support three modes
- noatime: set only when the file is created and when `SetAttr` is actively called, while accessing and modifying the file usually does not affect the Atime value, this is the default behavior
- relatime: update inode access times relative to modify or change time. Access time is only updated if the previous access time was earlier than the current modify or change time, or the file's last access time is always updated if it is more than 1 day old
- strictatime: always update atime on access
- Nlink
- Directory file: initial value is 2 ('.' and '..'), add 1 for each subdirectory
- Other files: initial value is 1, add 1 for each hard link created
Expand Down
3 changes: 3 additions & 0 deletions docs/en/reference/command_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ interval (in seconds) to send heartbeat; it's recommended that all clients use t
`--no-bgjob`<br />
disable background jobs (clean-up, backup, etc.) (default: false)

`--atime-mode value`<br />
control atime behavior, support 3 modes, `noatime`, `relatime`, `strictatime`, default to `noatime`

#### Examples

```bash
Expand Down
5 changes: 4 additions & 1 deletion docs/zh_cn/development/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,10 @@ type Attr struct {

其中几个需要说明的字段:

- Atime/Atimensec:仅在文件创建和主动调用 `SetAttr` 时设置,平时访问与修改文件不影响 Atime 值
- Atime/Atimensec:目前支持三种模式
- noatime: 仅在文件创建和主动调用 `SetAttr` 时设置,平时访问与修改文件不影响 Atime 值,这是默认行为
- relatime: Mtime 或者 Ctime 比 Atime 新时,或者 Atime 超过 24 小时没有更新时更新 Atime
- strictatime: 一直更新 Atime
- Nlink:
- 目录文件:初始值为 2('.' 和 '..'),每有一个子目录 Nlink 值加 1
- 其他文件:初始值为 1,每创建一个硬链接 Nlink 值加 1
Expand Down
3 changes: 3 additions & 0 deletions docs/zh_cn/reference/command_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ Consul 注册中心地址 (默认:"127.0.0.1:8500")
`--no-bgjob`<br />
禁用后台作业(清理、备份等)(默认:false)

`--atime-mode value`<br />
控制如何更新 atime,支持 3 种模式,`noatime`,`relatime`,`strictatime`,默认使用 `noatime`

#### 示例

```shell
Expand Down
23 changes: 20 additions & 3 deletions pkg/meta/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ type engine interface {
scanPendingFiles(Context, pendingFileScan) error

GetSession(sid uint64, detail bool) (*Session, error)
touchAtime(ctx Context, inode Ino, attr *Attr) syscall.Errno
}

type trashSliceScan func(ss []Slice, ts int64) (clean bool, err error)
Expand Down Expand Up @@ -1494,10 +1495,19 @@ func (m *baseMeta) Rename(ctx Context, parentSrc Ino, nameSrc string, parentDst
return st
}

func (m *baseMeta) Open(ctx Context, inode Ino, flags uint32, attr *Attr) syscall.Errno {
func (m *baseMeta) Open(ctx Context, inode Ino, flags uint32, attr *Attr) (rerr syscall.Errno) {
if m.conf.ReadOnly && flags&(syscall.O_WRONLY|syscall.O_RDWR|syscall.O_TRUNC|syscall.O_APPEND) != 0 {
return syscall.EROFS
}

defer func() {
if rerr == 0 {
if err := m.en.touchAtime(ctx, inode, attr); err != 0 {
logger.Warnf("open %v update atime: %s", inode, err)
}
}
}()

if m.conf.OpenCache > 0 && m.of.OpenCheck(inode, attr) {
return 0
}
Expand Down Expand Up @@ -1561,9 +1571,16 @@ func (m *baseMeta) Close(ctx Context, inode Ino) syscall.Errno {
return 0
}

func (m *baseMeta) Readdir(ctx Context, inode Ino, plus uint8, entries *[]*Entry) syscall.Errno {
inode = m.checkRoot(inode)
func (m *baseMeta) Readdir(ctx Context, inode Ino, plus uint8, entries *[]*Entry) (rerr syscall.Errno) {
var attr Attr
defer func() {
if rerr == 0 {
if err := m.en.touchAtime(ctx, inode, &attr); err != 0 {
logger.Warnf("readdir %v update atime: %s", inode, err)
}
}
}()
eryugey marked this conversation as resolved.
Show resolved Hide resolved
inode = m.checkRoot(inode)
if err := m.GetAttr(ctx, inode, &attr); err != 0 {
return err
}
Expand Down
1 change: 1 addition & 0 deletions pkg/meta/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type Config struct {
Heartbeat time.Duration
MountPoint string
Subdir string
AtimeMode string
}

func DefaultConf() *Config {
Expand Down
59 changes: 58 additions & 1 deletion pkg/meta/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -2113,7 +2113,14 @@ func (m *redisMeta) doDeleteSustainedInode(sid uint64, inode Ino) error {
return err
}

func (m *redisMeta) Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) syscall.Errno {
func (m *redisMeta) Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) (rerr syscall.Errno) {
defer func() {
if rerr == 0 {
if err := m.touchAtime(ctx, inode, nil); err != 0 {
logger.Warnf("read %v update atime: %s", inode, err)
}
}
}()
SandyXSD marked this conversation as resolved.
Show resolved Hide resolved
f := m.of.find(inode)
if f != nil {
f.RLock()
Expand Down Expand Up @@ -4240,3 +4247,53 @@ func (m *redisMeta) doAttachDirNode(ctx Context, parent Ino, dstIno Ino, name st
return err
}, m.inodeKey(parent), m.entryKey(parent)))
}

// caller makes sure inode is not special inode.
func (m *redisMeta) touchAtime(ctx Context, ino Ino, cur *Attr) (rerr syscall.Errno) {
if (m.conf.AtimeMode != StrictAtime && m.conf.AtimeMode != RelAtime) || m.conf.ReadOnly {
return 0
}

var attr *Attr
newAttr := &Attr{}
if cur != nil {
attr = cur
} else if m.of.Check(ino, newAttr) {
attr = newAttr
}

SandyXSD marked this conversation as resolved.
Show resolved Hide resolved
now := time.Now()
if attr != nil && !m.atimeNeedsUpdate(attr, now) {
return 0
}

updated := false
defer func() {
if rerr == 0 && m.of.IsOpen(ino) && updated {
m.of.Update(ino, attr)
}
}()

return errno(m.txn(ctx, func(tx *redis.Tx) error {
if attr == nil {
davies marked this conversation as resolved.
Show resolved Hide resolved
attr = newAttr
}
a, err := tx.Get(ctx, m.inodeKey(ino)).Bytes()
if err != nil {
return err
}
m.parseAttr(a, attr)

if !m.atimeNeedsUpdate(attr, now) {
return nil
}
attr.Atime = now.Unix()
attr.Atimensec = uint32(now.Nanosecond())
davies marked this conversation as resolved.
Show resolved Hide resolved
_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
pipe.Set(ctx, m.inodeKey(ino), m.marshal(attr), 0)
return nil
})
updated = true
return err
}, m.inodeKey(ino)))
}
58 changes: 57 additions & 1 deletion pkg/meta/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -2062,7 +2062,14 @@ func (m *dbMeta) doDeleteSustainedInode(sid uint64, inode Ino) error {
return err
}

func (m *dbMeta) Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) syscall.Errno {
func (m *dbMeta) Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) (rerr syscall.Errno) {
defer func() {
if rerr == 0 {
if err := m.touchAtime(ctx, inode, nil); err != 0 {
logger.Warnf("read %v update atime: %s", inode, err)
}
}
}()
f := m.of.find(inode)
if f != nil {
f.RLock()
Expand Down Expand Up @@ -3900,3 +3907,52 @@ func (m *dbMeta) doAttachDirNode(ctx Context, parent Ino, inode Ino, name string
return err
}, parent))
}

func (m *dbMeta) touchAtime(ctx Context, ino Ino, cur *Attr) (rerr syscall.Errno) {
if (m.conf.AtimeMode != StrictAtime && m.conf.AtimeMode != RelAtime) || m.conf.ReadOnly {
return 0
}

var curNode = node{Inode: ino}
var attr *Attr
newAttr := &Attr{}
if cur != nil {
attr = cur
} else if m.of.Check(ino, newAttr) {
attr = newAttr
}

now := time.Now()
if attr != nil && !m.atimeNeedsUpdate(attr, now) {
return 0
}

updated := false
defer func() {
if rerr == 0 && m.of.IsOpen(ino) && updated {
m.of.Update(ino, attr)
}
}()

return errno(m.txn(func(s *xorm.Session) error {
if attr == nil {
attr = newAttr
}
ok, err := s.ForUpdate().Get(&curNode)
if err != nil {
return err
}
if !ok {
return syscall.ENOENT
}
m.parseAttr(&curNode, attr)

if !m.atimeNeedsUpdate(attr, now) {
return nil
}
curNode.Atime = now.Unix()*1e6 + int64(now.Nanosecond())/1e3
_, err = s.Cols("atime").Update(&curNode, &node{Inode: ino})
updated = true
return err
}))
}
55 changes: 54 additions & 1 deletion pkg/meta/tkv.go
Original file line number Diff line number Diff line change
Expand Up @@ -1816,7 +1816,14 @@ func (m *kvMeta) doDeleteSustainedInode(sid uint64, inode Ino) error {
return err
}

func (m *kvMeta) Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) syscall.Errno {
func (m *kvMeta) Read(ctx Context, inode Ino, indx uint32, slices *[]Slice) (rerr syscall.Errno) {
defer func() {
if rerr == 0 {
if err := m.touchAtime(ctx, inode, nil); err != 0 {
logger.Warnf("read %v update atime: %s", inode, err)
}
}
}()
f := m.of.find(inode)
if f != nil {
f.RLock()
Expand Down Expand Up @@ -3380,3 +3387,49 @@ func (m *kvMeta) doAttachDirNode(ctx Context, parent Ino, inode Ino, name string
return nil
}, parent))
}

func (m *kvMeta) touchAtime(_ctx Context, ino Ino, cur *Attr) (rerr syscall.Errno) {
if (m.conf.AtimeMode != StrictAtime && m.conf.AtimeMode != RelAtime) || m.conf.ReadOnly {
return 0
}

var attr *Attr
newAttr := &Attr{}
if cur != nil {
attr = cur
} else if m.of.Check(ino, newAttr) {
attr = newAttr
}

now := time.Now()
if attr != nil && !m.atimeNeedsUpdate(attr, now) {
return 0
}

updated := false
defer func() {
if rerr == 0 && m.of.IsOpen(ino) && updated {
m.of.Update(ino, attr)
}
}()

return errno(m.client.txn(func(tx *kvTxn) error {
if attr == nil {
attr = newAttr
}
a := tx.get(m.inodeKey(ino))
if a == nil {
return syscall.ENOENT
}
m.parseAttr(a, attr)

if !m.atimeNeedsUpdate(attr, now) {
return nil
}
attr.Atime = now.Unix()
attr.Atimensec = uint32(now.Nanosecond())
tx.set(m.inodeKey(ino), m.marshal(attr))
updated = true
return nil
}, 0))
}
34 changes: 34 additions & 0 deletions pkg/meta/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ const (
CLONE_MODE_CAN_OVERWRITE = 0x01
CLONE_MODE_PRESERVE_ATTR = 0x02
CLONE_MODE_PRESERVE_HARDLINKS = 0x08

// atime mode
NoAtime = "noatime"
RelAtime = "relatime"
StrictAtime = "strictatime"
)

type msgCallbacks struct {
Expand Down Expand Up @@ -553,3 +558,32 @@ func (m *baseMeta) getTreeSummary(ctx Context, tree *TreeSummary, depth, topN ui
}
return 0
}

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)
}

// 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
}
Loading