GoPLS Viewer

Home|gopls/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
5package imports
6
7import (
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
33type directoryPackageStatus int
34
35const (
36    _ directoryPackageStatus = iota
37    directoryScanned
38    nameLoaded
39    exportsLoaded
40)
41
42type 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.
72func (info *directoryPackageInforeachedStatus(target directoryPackageStatus) (boolerror) {
73    if info.err == nil {
74        return info.status >= targetnil
75    }
76    if info.status == target {
77        return trueinfo.err
78    }
79    return truenil
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)
97type 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
104type 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.
109func (d *dirInfoCacheScanAndListen(ctx context.Contextlistener cacheListener) func() {
110    ctxcancel := 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 := 0i < maxInFlighti++ {
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(keyskey)
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.listenerscookie)
143        d.mu.Unlock()
144        for i := 0i < maxInFlighti++ {
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 vok := d.Load(k); ok {
157            listener(v)
158        }
159    }
160
161    return stop
162}
163
164// Store stores the package info for dir.
165func (d *dirInfoCacheStore(dir stringinfo 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(listenersl)
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.
183func (d *dirInfoCacheLoad(dir string) (directoryPackageInfobool) {
184    d.mu.Lock()
185    defer d.mu.Unlock()
186    infook := d.dirs[dir]
187    if !ok {
188        return directoryPackageInfo{}, false
189    }
190    return *infotrue
191}
192
193// Keys returns the keys currently present in d.
194func (d *dirInfoCacheKeys() (keys []string) {
195    d.mu.Lock()
196    defer d.mu.Unlock()
197    for key := range d.dirs {
198        keys = append(keyskey)
199    }
200    return keys
201}
202
203func (d *dirInfoCacheCachePackageName(info directoryPackageInfo) (stringerror) {
204    if loadederr := info.reachedStatus(nameLoaded); loaded {
205        return info.packageNameerr
206    }
207    if scannederr := info.reachedStatus(directoryScanned); !scanned || err != nil {
208        return ""fmt.Errorf("cannot read package name, scan error: %v"err)
209    }
210    info.packageNameinfo.err = packageDirToName(info.dir)
211    info.status = nameLoaded
212    d.Store(info.dirinfo)
213    return info.packageNameinfo.err
214}
215
216func (d *dirInfoCacheCacheExports(ctx context.Contextenv *ProcessEnvinfo directoryPackageInfo) (string, []stringerror) {
217    if reached_ := info.reachedStatus(exportsLoaded); reached {
218        return info.packageNameinfo.exportsinfo.err
219    }
220    if reachederr := info.reachedStatus(nameLoaded); reached && err != nil {
221        return ""nilerr
222    }
223    info.packageNameinfo.exportsinfo.err = loadExportsFromFiles(ctxenvinfo.dirfalse)
224    if info.err == context.Canceled || info.err == context.DeadlineExceeded {
225        return info.packageNameinfo.exportsinfo.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.dirinfo)
235    return info.packageNameinfo.exportsinfo.err
236}
237
MembersX
dirInfoCache.dirs
dirInfoCache.ScanAndListen.sema
dirInfoCache.Keys.keys
directoryPackageInfo
directoryPackageInfo.nonCanonicalImportPath
dirInfoCache.mu
dirInfoCache.ScanAndListen.maxInFlight
dirInfoCache.CacheExports
directoryPackageInfo.moduleName
directoryPackageInfo.reachedStatus
dirInfoCache.ScanAndListen.cancel
dirInfoCache.ScanAndListen.i
dirInfoCache.ScanAndListen.RangeStmt_4747.BlockStmt.ok
dirInfoCache.CacheExports.reached
directoryPackageInfo.reachedStatus.info
dirInfoCache.Store.RangeStmt_5104.l
directoryPackageInfo.err
directoryPackageInfo.packageName
dirInfoCache.Store.listeners
dirInfoCache.ScanAndListen.keys
dirInfoCache.ScanAndListen.BlockStmt.i
dirInfoCache.Keys
dirInfoCache.CachePackageName.d
dirInfoCache.CacheExports.info
directoryPackageInfo.moduleDir
dirInfoCache.ScanAndListen.d
dirInfoCache.ScanAndListen
dirInfoCache.Store
dirInfoCache.Store.dir
dirInfoCache.listeners
cacheListener
dirInfoCache.CacheExports.env
dirInfoCache.CacheExports._
directoryPackageInfo.status
dirInfoCache.CacheExports.d
dirInfoCache.ScanAndListen.cookie
dirInfoCache.ScanAndListen.RangeStmt_4747.k
dirInfoCache.Load.d
dirInfoCache.CachePackageName.scanned
dirInfoCache
dirInfoCache.ScanAndListen.ctx
directoryPackageInfo.rootType
directoryPackageInfo.reachedStatus.target
dirInfoCache.CachePackageName.info
dirInfoCache.CachePackageName.loaded
dirInfoCache.Store.d
dirInfoCache.Keys.d
dirInfoCache.ScanAndListen.RangeStmt_4747.BlockStmt.v
dirInfoCache.Store.BlockStmt.RangeStmt_5203.l
dirInfoCache.Keys.RangeStmt_5670.key
dirInfoCache.CacheExports.ctx
dirInfoCache.CacheExports.err
dirInfoCache.ScanAndListen.RangeStmt_4336.key
dirInfoCache.Store.info
dirInfoCache.Load
directoryPackageInfo.exports
dirInfoCache.ScanAndListen.listener
dirInfoCache.Load.dir
dirInfoCache.CachePackageName
dirInfoCache.CachePackageName.err
directoryPackageStatus
directoryPackageInfo.dir
Members
X