Skip to content

Commit

Permalink
vfs: introduce --atime-mode option to control atime update behavior
Browse files Browse the repository at this point in the history
Currently juicefs doesn't update atime on access, so introduce
--atime-mode option to select how to update atime. And support three
modes:

- noatime: don't update atime
- relatime: update atime relative to motify or change time
- strictatime: always update atime

Fixes: #3240
Signed-off-by: Eryu Guan <[email protected]>
  • Loading branch information
eryugey committed Apr 25, 2023
1 parent 74d398b commit 3157f9b
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 2 deletions.
4 changes: 4 additions & 0 deletions cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,10 @@ func clientFlags() []cli.Flag {
Name: "subdir",
Usage: "mount a sub-directory as root",
},
&cli.StringFlag{
Name: "atime-mode",
Usage: "when to update atime, supported mode includes: noatime (default), relatime, strictatime",
},
}
}

Expand Down
6 changes: 6 additions & 0 deletions cmd/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,12 @@ func getVfsConf(c *cli.Context, metaConf *meta.Config, format *meta.Format, chun
if cfg.BackupMeta > 0 && cfg.BackupMeta < time.Minute*5 {
logger.Fatalf("backup-meta should not be less than 5 minutes: %s", cfg.BackupMeta)
}
atimeMode := c.String("atime-mode")
if atimeMode != vfs.RelAtime && atimeMode != vfs.StrictAtime {
logger.Warnf("unknown atime-mode \"%s\", changed to %s", atimeMode, vfs.NoAtime)
atimeMode = vfs.NoAtime
}
cfg.AtimeMode = atimeMode
return cfg
}

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
62 changes: 62 additions & 0 deletions pkg/vfs/vfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const (
maxName = meta.MaxName
maxSymlink = 4096
maxFileSize = meta.ChunkSize << 31

NoAtime = "noatime"
RelAtime = "relatime"
StrictAtime = "strictatime"
)

type Port struct {
Expand All @@ -61,6 +65,7 @@ type Config struct {
FastResolve bool `json:",omitempty"`
AccessLog string `json:",omitempty"`
HideInternal bool
AtimeMode string
}

var (
Expand Down Expand Up @@ -564,6 +569,12 @@ func (v *VFS) Read(ctx Context, ino Ino, buf []byte, off uint64, fh uint64) (n i
if err == syscall.ENOENT {
err = syscall.EBADF
}

if err == 0 {
if aerr := v.touchAtime(ctx, ino); aerr != 0 {
logger.Warnf("read %d update atime: %v", ino, aerr)
}
}
h.removeOp(ctx)
return
}
Expand Down Expand Up @@ -731,6 +742,9 @@ func (v *VFS) CopyFileRange(ctx Context, nodeIn Ino, fhIn, offIn uint64, nodeOut
if err == 0 {
v.reader.Invalidate(nodeOut, offOut, size)
v.invalidateLength(nodeOut)
if err := v.touchAtime(ctx, nodeIn); err != 0 {
logger.Warnf("copy_file_range %d update atime: %v", nodeIn, err)
}
}
return
}
Expand Down Expand Up @@ -995,6 +1009,54 @@ func (v *VFS) cleanupModified() {
}
}

func (v *VFS) atimeNeedsUpdate(attr *Attr, now time.Time) bool {
if v.Conf.AtimeMode == RelAtime && relatimeNeedUpdate(attr, now) {
return true
}

return v.Conf.AtimeMode == StrictAtime && !now.Equal(time.Unix(attr.Atime, int64(attr.Atimensec)))
}

// caller makes sure inode is not special inode.
func (v *VFS) touchAtime(ctx Context, ino Ino) syscall.Errno {
if (v.Conf.AtimeMode != StrictAtime && v.Conf.AtimeMode != RelAtime) || v.Conf.Meta.ReadOnly {
return 0
}

var origAttr = &Attr{}
if err := v.Meta.GetAttr(ctx, ino, origAttr); err != 0 {
return err
}
now := time.Now()
if !v.atimeNeedsUpdate(origAttr, now) {
return 0
}

return v.Meta.SetAttr(ctx, ino, meta.SetAttrAtimeNow, 0, &Attr{})
}

// 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
if now.Sub(atime) > 24*time.Hour {
return true
}

return false
}
func initVFSMetrics(v *VFS, writer DataWriter, reader DataReader, registerer prometheus.Registerer) {
if registerer == nil {
return
Expand Down
83 changes: 83 additions & 0 deletions pkg/vfs/vfs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -830,3 +830,86 @@ func TestInternalFile(t *testing.T) {
t.Fatalf("result: %s", string(resp[:n]))
}
}

func TestAtime(t *testing.T) {
v, _ := createTestVFS()
ctx := NewLogContext(meta.NewContext(10, 1, []uint32{2}))

// noatime by default
fe, fh, e := v.Create(ctx, 1, "f1", 0755, 0, syscall.O_RDWR)
if e != 0 {
t.Fatalf("create file: %s", e)
}
if fe2, e := v.SetAttr(ctx, fe.Inode, meta.SetAttrAtime, 0, 0, 0, 0, 1234, 0, 5678, 0, 0); e != 0 {
t.Fatalf("setattr f1: %s", e)
} else if fe2.Attr.Atime != 1234 || fe2.Attr.Atimensec != 5678 {
t.Fatalf("setattr f1: %+v", fe2.Attr)
}
var buf = make([]byte, 4096)
if _, e := v.Read(ctx, fe.Inode, buf, 0, fh); e != 0 {
t.Fatalf("read file: %s", e)
}
if fe, e := v.GetAttr(ctx, fe.Inode, 0); e != 0 || fe.Attr.Atime != 1234 || fe.Attr.Atimensec != 5678 {
t.Fatalf("getattr after read f1: %s atime: %v, atimensec %v", e, fe.Attr.Atime, fe.Attr.Atimensec)
}

// set relatime
v.Conf.AtimeMode = RelAtime
fe, fh, e = v.Create(ctx, 1, "f2", 0755, 0, syscall.O_RDWR)
if e != 0 {
t.Fatalf("create file: %s", e)
}
if fe2, e := v.SetAttr(ctx, fe.Inode, meta.SetAttrAtime, 0, 0, 0, 0, 1234, 0, 5678, 0, 0); e != 0 {
t.Fatalf("setattr f2: %s", e)
} else if fe2.Attr.Atime != 1234 || fe2.Attr.Atimensec != 5678 {
t.Fatalf("setattr f2: %+v", fe2.Attr)
}
if _, e := v.Read(ctx, fe.Inode, buf, 0, fh); e != 0 {
t.Fatalf("read file: %s", e)
}
// older than 24h, update atime on read
if fe, e := v.GetAttr(ctx, fe.Inode, 0); e != 0 || fe.Attr.Atime == 1234 {
t.Fatalf("getattr after read f2: %s atime: %v, atimensec %v", e, fe.Attr.Atime, fe.Attr.Atimensec)
}
now := time.Now()
if fe2, e := v.SetAttr(ctx, fe.Inode, meta.SetAttrMtime, 0, 0, 0, 0, 0, now.Unix(), 0, uint32(now.Nanosecond()), 0); e != 0 {
t.Fatalf("setattr f2: %s", e)
} else if fe2.Attr.Mtime != now.Unix() || fe2.Attr.Mtimensec != uint32(now.Nanosecond()) {
t.Fatalf("setattr f2: %+v", fe2.Attr)
}
time.Sleep(time.Second)
if _, e := v.Read(ctx, fe.Inode, buf, 0, fh); e != 0 {
t.Fatalf("read file: %s", e)
}
// mtime > atime, update atime on access
if fe, e := v.GetAttr(ctx, fe.Inode, 0); e != 0 || fe.Attr.Atime < now.Unix() {
t.Fatalf("getattr after access f2: %s atime: %v, now %v", e, fe.Attr.Atime, now.Unix())
}

// set strictatime
v.Conf.AtimeMode = StrictAtime
fe, fh, e = v.Create(ctx, 1, "f3", 0755, 0, syscall.O_RDWR)
if e != 0 {
t.Fatalf("create file: %s", e)
}
if fe2, e := v.SetAttr(ctx, fe.Inode, meta.SetAttrAtime, 0, 0, 0, 0, 1234, 0, 5678, 0, 0); e != 0 {
t.Fatalf("setattr f3: %s", e)
} else if fe2.Attr.Atime != 1234 || fe2.Attr.Atimensec != 5678 {
t.Fatalf("setattr f3: %+v", fe2.Attr)
}
if _, e := v.Read(ctx, fe.Inode, buf, 0, fh); e != 0 {
t.Fatalf("read file: %s", e)
}
// always update atime on read
if fe, e := v.GetAttr(ctx, fe.Inode, 0); e != 0 || fe.Attr.Atime == 1234 {
t.Fatalf("getattr after read f3: %s atime: %v, atimensec %v", e, fe.Attr.Atime, fe.Attr.Atimensec)
}
now = time.Now()
time.Sleep(time.Second)
if _, e := v.Read(ctx, fe.Inode, buf, 0, fh); e != 0 {
t.Fatalf("read file: %s", e)
}
if fe, e := v.GetAttr(ctx, fe.Inode, 0); e != 0 || fe.Attr.Atime < now.Unix() {
t.Fatalf("getattr after access f3: %s atime: %v, now %v", e, fe.Attr.Atime, now.Unix())
}
}

0 comments on commit 3157f9b

Please sign in to comment.