17171: Exposes bug with a test.
[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 // Child rejects (with ErrInvalidArgument) calls to add/replace
70 // children, instead calling loadOne when a non-existing child is
71 // looked up.
72 func (ln *lookupnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
73         ln.staleLock.Lock()
74         defer ln.staleLock.Unlock()
75         checkTime := time.Now()
76         var existing inode
77         var err error
78         if ln.stale(ln.staleAll) && ln.stale(ln.staleOne[name]) {
79                 existing, err = ln.treenode.Child(name, func(inode) (inode, error) {
80                         return ln.loadOne(ln, name)
81                 })
82                 if err == nil && existing != nil {
83                         if ln.staleOne == nil {
84                                 ln.staleOne = map[string]time.Time{name: checkTime}
85                         } else {
86                                 ln.staleOne[name] = checkTime
87                         }
88                 }
89         } else {
90                 existing, err = ln.treenode.Child(name, nil)
91                 if err != nil && !os.IsNotExist(err) {
92                         return existing, err
93                 }
94         }
95         if replace != nil {
96                 // Let the callback try to delete or replace the
97                 // existing node; if it does, return
98                 // ErrInvalidArgument.
99                 if tryRepl, err := replace(existing); err != nil {
100                         // Propagate error from callback
101                         return existing, err
102                 } else if tryRepl != existing {
103                         return existing, ErrInvalidArgument
104                 }
105         }
106         // Return original error from ln.treenode.Child() (it might be
107         // ErrNotExist).
108         return existing, err
109 }