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