Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / mvdan.cc / gofumpt@v0.0.0-20200802201014-ab5a8192947d / gofumports / internal / imports / mod_cache.go
1 package imports
2
3 import (
4         "context"
5         "fmt"
6         "sync"
7
8         "mvdan.cc/gofumpt/gofumports/internal/gopathwalk"
9 )
10
11 // To find packages to import, the resolver needs to know about all of the
12 // the packages that could be imported. This includes packages that are
13 // already in modules that are in (1) the current module, (2) replace targets,
14 // and (3) packages in the module cache. Packages in (1) and (2) may change over
15 // time, as the client may edit the current module and locally replaced modules.
16 // The module cache (which includes all of the packages in (3)) can only
17 // ever be added to.
18 //
19 // The resolver can thus save state about packages in the module cache
20 // and guarantee that this will not change over time. To obtain information
21 // about new modules added to the module cache, the module cache should be
22 // rescanned.
23 //
24 // It is OK to serve information about modules that have been deleted,
25 // as they do still exist.
26 // TODO(suzmue): can we share information with the caller about
27 // what module needs to be downloaded to import this package?
28
29 type directoryPackageStatus int
30
31 const (
32         _ directoryPackageStatus = iota
33         directoryScanned
34         nameLoaded
35         exportsLoaded
36 )
37
38 type directoryPackageInfo struct {
39         // status indicates the extent to which this struct has been filled in.
40         status directoryPackageStatus
41         // err is non-nil when there was an error trying to reach status.
42         err error
43
44         // Set when status >= directoryScanned.
45
46         // dir is the absolute directory of this package.
47         dir      string
48         rootType gopathwalk.RootType
49         // nonCanonicalImportPath is the package's expected import path. It may
50         // not actually be importable at that path.
51         nonCanonicalImportPath string
52
53         // Module-related information.
54         moduleDir  string // The directory that is the module root of this dir.
55         moduleName string // The module name that contains this dir.
56
57         // Set when status >= nameLoaded.
58
59         packageName string // the package name, as declared in the source.
60
61         // Set when status >= exportsLoaded.
62
63         exports []string
64 }
65
66 // reachedStatus returns true when info has a status at least target and any error associated with
67 // an attempt to reach target.
68 func (info *directoryPackageInfo) reachedStatus(target directoryPackageStatus) (bool, error) {
69         if info.err == nil {
70                 return info.status >= target, nil
71         }
72         if info.status == target {
73                 return true, info.err
74         }
75         return true, nil
76 }
77
78 // dirInfoCache is a concurrency safe map for storing information about
79 // directories that may contain packages.
80 //
81 // The information in this cache is built incrementally. Entries are initialized in scan.
82 // No new keys should be added in any other functions, as all directories containing
83 // packages are identified in scan.
84 //
85 // Other functions, including loadExports and findPackage, may update entries in this cache
86 // as they discover new things about the directory.
87 //
88 // The information in the cache is not expected to change for the cache's
89 // lifetime, so there is no protection against competing writes. Users should
90 // take care not to hold the cache across changes to the underlying files.
91 //
92 // TODO(suzmue): consider other concurrency strategies and data structures (RWLocks, sync.Map, etc)
93 type dirInfoCache struct {
94         mu sync.Mutex
95         // dirs stores information about packages in directories, keyed by absolute path.
96         dirs      map[string]*directoryPackageInfo
97         listeners map[*int]cacheListener
98 }
99
100 type cacheListener func(directoryPackageInfo)
101
102 // ScanAndListen calls listener on all the items in the cache, and on anything
103 // newly added. The returned stop function waits for all in-flight callbacks to
104 // finish and blocks new ones.
105 func (d *dirInfoCache) ScanAndListen(ctx context.Context, listener cacheListener) func() {
106         ctx, cancel := context.WithCancel(ctx)
107
108         // Flushing out all the callbacks is tricky without knowing how many there
109         // are going to be. Setting an arbitrary limit makes it much easier.
110         const maxInFlight = 10
111         sema := make(chan struct{}, maxInFlight)
112         for i := 0; i < maxInFlight; i++ {
113                 sema <- struct{}{}
114         }
115
116         cookie := new(int) // A unique ID we can use for the listener.
117
118         // We can't hold mu while calling the listener.
119         d.mu.Lock()
120         var keys []string
121         for key := range d.dirs {
122                 keys = append(keys, key)
123         }
124         d.listeners[cookie] = func(info directoryPackageInfo) {
125                 select {
126                 case <-ctx.Done():
127                         return
128                 case <-sema:
129                 }
130                 listener(info)
131                 sema <- struct{}{}
132         }
133         d.mu.Unlock()
134
135         stop := func() {
136                 cancel()
137                 d.mu.Lock()
138                 delete(d.listeners, cookie)
139                 d.mu.Unlock()
140                 for i := 0; i < maxInFlight; i++ {
141                         <-sema
142                 }
143         }
144
145         // Process the pre-existing keys.
146         for _, k := range keys {
147                 select {
148                 case <-ctx.Done():
149                         return stop
150                 default:
151                 }
152                 if v, ok := d.Load(k); ok {
153                         listener(v)
154                 }
155         }
156
157         return stop
158 }
159
160 // Store stores the package info for dir.
161 func (d *dirInfoCache) Store(dir string, info directoryPackageInfo) {
162         d.mu.Lock()
163         _, old := d.dirs[dir]
164         d.dirs[dir] = &info
165         var listeners []cacheListener
166         for _, l := range d.listeners {
167                 listeners = append(listeners, l)
168         }
169         d.mu.Unlock()
170
171         if !old {
172                 for _, l := range listeners {
173                         l(info)
174                 }
175         }
176 }
177
178 // Load returns a copy of the directoryPackageInfo for absolute directory dir.
179 func (d *dirInfoCache) Load(dir string) (directoryPackageInfo, bool) {
180         d.mu.Lock()
181         defer d.mu.Unlock()
182         info, ok := d.dirs[dir]
183         if !ok {
184                 return directoryPackageInfo{}, false
185         }
186         return *info, true
187 }
188
189 // Keys returns the keys currently present in d.
190 func (d *dirInfoCache) Keys() (keys []string) {
191         d.mu.Lock()
192         defer d.mu.Unlock()
193         for key := range d.dirs {
194                 keys = append(keys, key)
195         }
196         return keys
197 }
198
199 func (d *dirInfoCache) CachePackageName(info directoryPackageInfo) (string, error) {
200         if loaded, err := info.reachedStatus(nameLoaded); loaded {
201                 return info.packageName, err
202         }
203         if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil {
204                 return "", fmt.Errorf("cannot read package name, scan error: %v", err)
205         }
206         info.packageName, info.err = packageDirToName(info.dir)
207         info.status = nameLoaded
208         d.Store(info.dir, info)
209         return info.packageName, info.err
210 }
211
212 func (d *dirInfoCache) CacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []string, error) {
213         if reached, _ := info.reachedStatus(exportsLoaded); reached {
214                 return info.packageName, info.exports, info.err
215         }
216         if reached, err := info.reachedStatus(nameLoaded); reached && err != nil {
217                 return "", nil, err
218         }
219         info.packageName, info.exports, info.err = loadExportsFromFiles(ctx, env, info.dir, false)
220         if info.err == context.Canceled || info.err == context.DeadlineExceeded {
221                 return info.packageName, info.exports, info.err
222         }
223         // The cache structure wants things to proceed linearly. We can skip a
224         // step here, but only if we succeed.
225         if info.status == nameLoaded || info.err == nil {
226                 info.status = exportsLoaded
227         } else {
228                 info.status = nameLoaded
229         }
230         d.Store(info.dir, info)
231         return info.packageName, info.exports, info.err
232 }