16535: Support Sync() on customfilesystem.
[arvados.git] / sdk / go / arvados / fs_lookup.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package arvados
6
7 import (
8         "os"
9         "sync"
10         "time"
11 )
12
13 // lookupnode is a caching tree node that is initially empty and calls
14 // loadOne and loadAll to load/update child nodes as needed.
15 //
16 // See (*customFileSystem)MountUsers for example usage.
17 type lookupnode struct {
18         treenode
19         loadOne func(parent inode, name string) (inode, error)
20         loadAll func(parent inode) ([]inode, error)
21         stale   func(time.Time) bool
22
23         // internal fields
24         staleLock sync.Mutex
25         staleAll  time.Time
26         staleOne  map[string]time.Time
27 }
28
29 // Sync flushes pending writes for loaded children and, if successful,
30 // triggers a reload on next lookup.
31 func (ln *lookupnode) Sync() error {
32         err := ln.treenode.Sync()
33         if err != nil {
34                 return err
35         }
36         ln.staleLock.Lock()
37         ln.staleAll = time.Time{}
38         ln.staleOne = nil
39         ln.staleLock.Unlock()
40         return nil
41 }
42
43 func (ln *lookupnode) Readdir() ([]os.FileInfo, error) {
44         ln.staleLock.Lock()
45         defer ln.staleLock.Unlock()
46         checkTime := time.Now()
47         if ln.stale(ln.staleAll) {
48                 all, err := ln.loadAll(ln)
49                 if err != nil {
50                         return nil, err
51                 }
52                 for _, child := range all {
53                         _, err = ln.treenode.Child(child.FileInfo().Name(), func(inode) (inode, error) {
54                                 return child, nil
55                         })
56                         if err != nil {
57                                 return nil, err
58                         }
59                 }
60                 ln.staleAll = checkTime
61                 // No value in ln.staleOne can make a difference to an
62                 // "entry is stale?" test now, because no value is
63                 // newer than ln.staleAll. Reclaim memory.
64                 ln.staleOne = nil
65         }
66         return ln.treenode.Readdir()
67 }
68
69 func (ln *lookupnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
70         ln.staleLock.Lock()
71         defer ln.staleLock.Unlock()
72         checkTime := time.Now()
73         if ln.stale(ln.staleAll) && ln.stale(ln.staleOne[name]) {
74                 _, err := ln.treenode.Child(name, func(inode) (inode, error) {
75                         return ln.loadOne(ln, name)
76                 })
77                 if err != nil {
78                         return nil, err
79                 }
80                 if ln.staleOne == nil {
81                         ln.staleOne = map[string]time.Time{name: checkTime}
82                 } else {
83                         ln.staleOne[name] = checkTime
84                 }
85         }
86         return ln.treenode.Child(name, replace)
87 }