19192: Fix deadlock in lookupnode.
[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         "time"
10 )
11
12 // lookupnode is a caching tree node that is initially empty and calls
13 // loadOne and loadAll to load/update child nodes as needed.
14 //
15 // See (*customFileSystem)MountUsers for example usage.
16 type lookupnode struct {
17         treenode
18         loadOne func(parent inode, name string) (inode, error)
19         loadAll func(parent inode) ([]inode, error)
20         stale   func(time.Time) bool
21
22         // internal fields
23         staleAll time.Time
24         staleOne map[string]time.Time
25 }
26
27 // Sync flushes pending writes for loaded children and, if successful,
28 // triggers a reload on next lookup.
29 func (ln *lookupnode) Sync() error {
30         err := ln.treenode.Sync()
31         if err != nil {
32                 return err
33         }
34         ln.Lock()
35         ln.staleAll = time.Time{}
36         ln.staleOne = nil
37         ln.Unlock()
38         return nil
39 }
40
41 func (ln *lookupnode) Readdir() ([]os.FileInfo, error) {
42         ln.Lock()
43         checkTime := time.Now()
44         if ln.stale(ln.staleAll) {
45                 all, err := ln.loadAll(ln)
46                 if err != nil {
47                         ln.Unlock()
48                         return nil, err
49                 }
50                 for _, child := range all {
51                         _, err = ln.treenode.Child(child.FileInfo().Name(), func(inode) (inode, error) {
52                                 return child, nil
53                         })
54                         if err != nil {
55                                 ln.Unlock()
56                                 return nil, err
57                         }
58                 }
59                 ln.staleAll = checkTime
60                 // No value in ln.staleOne can make a difference to an
61                 // "entry is stale?" test now, because no value is
62                 // newer than ln.staleAll. Reclaim memory.
63                 ln.staleOne = nil
64         }
65         ln.Unlock()
66         return ln.treenode.Readdir()
67 }
68
69 // Child rejects (with ErrInvalidOperation) 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         checkTime := time.Now()
74         var existing inode
75         var err error
76         if ln.stale(ln.staleAll) && ln.stale(ln.staleOne[name]) {
77                 existing, err = ln.treenode.Child(name, func(inode) (inode, error) {
78                         return ln.loadOne(ln, name)
79                 })
80                 if err == nil && existing != nil {
81                         if ln.staleOne == nil {
82                                 ln.staleOne = map[string]time.Time{name: checkTime}
83                         } else {
84                                 ln.staleOne[name] = checkTime
85                         }
86                 }
87         } else {
88                 existing, err = ln.treenode.Child(name, nil)
89                 if err != nil && !os.IsNotExist(err) {
90                         return existing, err
91                 }
92         }
93         if replace != nil {
94                 // Let the callback try to delete or replace the
95                 // existing node; if it does, return
96                 // ErrInvalidOperation.
97                 if tryRepl, err := replace(existing); err != nil {
98                         // Propagate error from callback
99                         return existing, err
100                 } else if tryRepl != existing {
101                         return existing, ErrInvalidOperation
102                 }
103         }
104         // Return original error from ln.treenode.Child() (it might be
105         // ErrNotExist).
106         return existing, err
107 }