18676: Merge branch 'main' into 18676-anon-token-support-v2-in-config
[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                         ln.treenode.Lock()
54                         _, err = ln.treenode.Child(child.FileInfo().Name(), func(inode) (inode, error) {
55                                 return child, nil
56                         })
57                         ln.treenode.Unlock()
58                         if err != nil {
59                                 return nil, err
60                         }
61                 }
62                 ln.staleAll = checkTime
63                 // No value in ln.staleOne can make a difference to an
64                 // "entry is stale?" test now, because no value is
65                 // newer than ln.staleAll. Reclaim memory.
66                 ln.staleOne = nil
67         }
68         return ln.treenode.Readdir()
69 }
70
71 // Child rejects (with ErrInvalidOperation) calls to add/replace
72 // children, instead calling loadOne when a non-existing child is
73 // looked up.
74 func (ln *lookupnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
75         ln.staleLock.Lock()
76         defer ln.staleLock.Unlock()
77         checkTime := time.Now()
78         var existing inode
79         var err error
80         if ln.stale(ln.staleAll) && ln.stale(ln.staleOne[name]) {
81                 existing, err = ln.treenode.Child(name, func(inode) (inode, error) {
82                         return ln.loadOne(ln, name)
83                 })
84                 if err == nil && existing != nil {
85                         if ln.staleOne == nil {
86                                 ln.staleOne = map[string]time.Time{name: checkTime}
87                         } else {
88                                 ln.staleOne[name] = checkTime
89                         }
90                 }
91         } else {
92                 existing, err = ln.treenode.Child(name, nil)
93                 if err != nil && !os.IsNotExist(err) {
94                         return existing, err
95                 }
96         }
97         if replace != nil {
98                 // Let the callback try to delete or replace the
99                 // existing node; if it does, return
100                 // ErrInvalidOperation.
101                 if tryRepl, err := replace(existing); err != nil {
102                         // Propagate error from callback
103                         return existing, err
104                 } else if tryRepl != existing {
105                         return existing, ErrInvalidOperation
106                 }
107         }
108         // Return original error from ln.treenode.Child() (it might be
109         // ErrNotExist).
110         return existing, err
111 }