@@ -30,16 +30,11 @@ import (
30
30
// both caused by populating the cache, albeit in slightly different ways.
31
31
//
32
32
// A high level list of TODOs:
33
- // - Write an additional benchmark for refreshing the directory state.
34
- // - Split scanning the module cache from other ModuleResolver functionality,
35
- // as it is the source of performance woes (and inconsistency).
36
- // - Allow sharing module cache state across multiple ModuleResolvers.
37
33
// - Optimize the scan itself, as there is some redundancy statting and
38
34
// reading go.mod files.
39
- // - Make it possible to reuse the current state while running a refresh in
40
- // the background.
41
- // - Fix context cancellation (again): if the context is cancelled while a
42
- // root is being walked, nothing stops that ongoing walk.
35
+ // - Invert the relationship between ProcessEnv and Resolver (see the
36
+ // docstring of ProcessEnv).
37
+ // - Make it easier to use an external resolver implementation.
43
38
//
44
39
// Smaller TODOs are annotated in the code below.
45
40
@@ -72,7 +67,11 @@ type ModuleResolver struct {
72
67
modsByDir []* gocommand.ModuleJSON // ...or by the number of path components in their Dir.
73
68
74
69
// Scanning state, populated by scan
75
- scanSema chan struct {} // prevents concurrent scans and guards scannedRoots
70
+
71
+ // scanSema prevents concurrent scans, and guards scannedRoots and the cache
72
+ // fields below (though the caches themselves are concurrency safe).
73
+ // Receive to acquire, send to release.
74
+ scanSema chan struct {}
76
75
scannedRoots map [gopathwalk.Root ]bool // if true, root has been walked
77
76
78
77
// Caches of directory info, populated by scans and scan callbacks
@@ -86,12 +85,16 @@ type ModuleResolver struct {
86
85
otherCache * DirInfoCache
87
86
}
88
87
88
+ // newModuleResolver returns a new module-aware goimports resolver.
89
+ //
90
+ // Note: use caution when modifying this constructor: changes must also be
91
+ // reflected in ModuleResolver.ClearForNewScan.
89
92
func newModuleResolver (e * ProcessEnv , moduleCacheCache * DirInfoCache ) (* ModuleResolver , error ) {
90
93
r := & ModuleResolver {
91
94
env : e ,
92
95
scanSema : make (chan struct {}, 1 ),
93
96
}
94
- r .scanSema <- struct {}{}
97
+ r .scanSema <- struct {}{} // release
95
98
96
99
goenv , err := r .env .goEnv ()
97
100
if err != nil {
@@ -265,10 +268,23 @@ func (r *ModuleResolver) initAllMods() error {
265
268
// It preserves the set of roots, but forgets about the set of directories.
266
269
// Though it forgets the set of module cache directories, it remembers their
267
270
// contents, since they are assumed to be immutable.
268
- func (r * ModuleResolver ) ClearForNewScan () {
269
- <- r .scanSema
270
- prevRoots := r .scannedRoots
271
- r .scannedRoots = map [gopathwalk.Root ]bool {}
271
+ func (r * ModuleResolver ) ClearForNewScan () Resolver {
272
+ <- r .scanSema // acquire r, to guard scannedRoots
273
+ r2 := & ModuleResolver {
274
+ env : r .env ,
275
+ dummyVendorMod : r .dummyVendorMod ,
276
+ moduleCacheDir : r .moduleCacheDir ,
277
+ roots : r .roots ,
278
+ mains : r .mains ,
279
+ mainByDir : r .mainByDir ,
280
+ modsByModPath : r .modsByModPath ,
281
+
282
+ scanSema : make (chan struct {}, 1 ),
283
+ scannedRoots : make (map [gopathwalk.Root ]bool ),
284
+ otherCache : NewDirInfoCache (),
285
+ moduleCacheCache : r .moduleCacheCache ,
286
+ }
287
+ r2 .scanSema <- struct {}{} // r2 must start released
272
288
// Invalidate root scans. We don't need to invalidate module cache roots,
273
289
// because they are immutable.
274
290
// (We don't support a use case where GOMODCACHE is cleaned in the middle of
@@ -278,12 +294,12 @@ func (r *ModuleResolver) ClearForNewScan() {
278
294
// Scanning for new directories in GOMODCACHE should be handled elsewhere,
279
295
// via a call to ScanModuleCache.
280
296
for _ , root := range r .roots {
281
- if root .Type == gopathwalk .RootModuleCache && prevRoots [root ] {
282
- r .scannedRoots [root ] = true
297
+ if root .Type == gopathwalk .RootModuleCache && r . scannedRoots [root ] {
298
+ r2 .scannedRoots [root ] = true
283
299
}
284
300
}
285
- r .otherCache = NewDirInfoCache ()
286
- r . scanSema <- struct {}{}
301
+ r .scanSema <- struct {}{} // release r
302
+ return r2
287
303
}
288
304
289
305
// ClearModuleInfo invalidates resolver state that depends on go.mod file
@@ -299,16 +315,27 @@ func (e *ProcessEnv) ClearModuleInfo() {
299
315
if r , ok := e .resolver .(* ModuleResolver ); ok {
300
316
resolver , resolverErr := newModuleResolver (e , e .ModCache )
301
317
if resolverErr == nil {
302
- <- r .scanSema // guards caches
318
+ <- r .scanSema // acquire ( guards caches)
303
319
resolver .moduleCacheCache = r .moduleCacheCache
304
320
resolver .otherCache = r .otherCache
305
- r .scanSema <- struct {}{}
321
+ r .scanSema <- struct {}{} // release
306
322
}
307
323
e .resolver = resolver
308
324
e .resolverErr = resolverErr
309
325
}
310
326
}
311
327
328
+ // UpdateResolver sets the resolver for the ProcessEnv to use in imports
329
+ // operations. Only for use with the result of [Resolver.ClearForNewScan].
330
+ //
331
+ // TODO(rfindley): this awkward API is a result of the (arguably) inverted
332
+ // relationship between configuration and state described in the doc comment
333
+ // for [ProcessEnv].
334
+ func (e * ProcessEnv ) UpdateResolver (r Resolver ) {
335
+ e .resolver = r
336
+ e .resolverErr = nil
337
+ }
338
+
312
339
// findPackage returns the module and directory from within the main modules
313
340
// and their dependencies that contains the package at the given import path,
314
341
// or returns nil, "" if no module is in scope.
@@ -580,9 +607,9 @@ func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error
580
607
select {
581
608
case <- ctx .Done ():
582
609
return
583
- case <- r .scanSema :
610
+ case <- r .scanSema : // acquire
584
611
}
585
- defer func () { r .scanSema <- struct {}{} }()
612
+ defer func () { r .scanSema <- struct {}{} }() // release
586
613
// We have the lock on r.scannedRoots, and no other scans can run.
587
614
for _ , root := range roots {
588
615
if ctx .Err () != nil {
0 commit comments