golang.org/x/tools/gopls@v0.15.3/internal/cache/imports.go (about) 1 // Copyright 2020 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 cache 6 7 import ( 8 "context" 9 "fmt" 10 "sync" 11 "time" 12 13 "golang.org/x/tools/gopls/internal/file" 14 "golang.org/x/tools/internal/event" 15 "golang.org/x/tools/internal/event/keys" 16 "golang.org/x/tools/internal/event/tag" 17 "golang.org/x/tools/internal/imports" 18 ) 19 20 // refreshTimer implements delayed asynchronous refreshing of state. 21 // 22 // See the [refreshTimer.schedule] documentation for more details. 23 type refreshTimer struct { 24 mu sync.Mutex 25 duration time.Duration 26 timer *time.Timer 27 refreshFn func() 28 } 29 30 // newRefreshTimer constructs a new refresh timer which schedules refreshes 31 // using the given function. 32 func newRefreshTimer(refresh func()) *refreshTimer { 33 return &refreshTimer{ 34 refreshFn: refresh, 35 } 36 } 37 38 // schedule schedules the refresh function to run at some point in the future, 39 // if no existing refresh is already scheduled. 40 // 41 // At a minimum, scheduled refreshes are delayed by 30s, but they may be 42 // delayed longer to keep their expected execution time under 2% of wall clock 43 // time. 44 func (t *refreshTimer) schedule() { 45 t.mu.Lock() 46 defer t.mu.Unlock() 47 48 if t.timer == nil { 49 // Don't refresh more than twice per minute. 50 delay := 30 * time.Second 51 // Don't spend more than ~2% of the time refreshing. 52 if adaptive := 50 * t.duration; adaptive > delay { 53 delay = adaptive 54 } 55 t.timer = time.AfterFunc(delay, func() { 56 start := time.Now() 57 t.refreshFn() 58 t.mu.Lock() 59 t.duration = time.Since(start) 60 t.timer = nil 61 t.mu.Unlock() 62 }) 63 } 64 } 65 66 // A sharedModCache tracks goimports state for GOMODCACHE directories 67 // (each session may have its own GOMODCACHE). 68 // 69 // This state is refreshed independently of view-specific imports state. 70 type sharedModCache struct { 71 mu sync.Mutex 72 caches map[string]*imports.DirInfoCache // GOMODCACHE -> cache content; never invalidated 73 timers map[string]*refreshTimer // GOMODCACHE -> timer 74 } 75 76 func (c *sharedModCache) dirCache(dir string) *imports.DirInfoCache { 77 c.mu.Lock() 78 defer c.mu.Unlock() 79 80 cache, ok := c.caches[dir] 81 if !ok { 82 cache = imports.NewDirInfoCache() 83 c.caches[dir] = cache 84 } 85 return cache 86 } 87 88 // refreshDir schedules a refresh of the given directory, which must be a 89 // module cache. 90 func (c *sharedModCache) refreshDir(ctx context.Context, dir string, logf func(string, ...any)) { 91 cache := c.dirCache(dir) 92 93 c.mu.Lock() 94 defer c.mu.Unlock() 95 timer, ok := c.timers[dir] 96 if !ok { 97 timer = newRefreshTimer(func() { 98 _, done := event.Start(ctx, "cache.sharedModCache.refreshDir", tag.Directory.Of(dir)) 99 defer done() 100 imports.ScanModuleCache(dir, cache, logf) 101 }) 102 c.timers[dir] = timer 103 } 104 105 timer.schedule() 106 } 107 108 // importsState tracks view-specific imports state. 109 type importsState struct { 110 ctx context.Context 111 modCache *sharedModCache 112 refreshTimer *refreshTimer 113 114 mu sync.Mutex 115 processEnv *imports.ProcessEnv 116 cachedModFileHash file.Hash 117 } 118 119 // newImportsState constructs a new imports state for running goimports 120 // functions via [runProcessEnvFunc]. 121 // 122 // The returned state will automatically refresh itself following a call to 123 // runProcessEnvFunc. 124 func newImportsState(backgroundCtx context.Context, modCache *sharedModCache, env *imports.ProcessEnv) *importsState { 125 s := &importsState{ 126 ctx: backgroundCtx, 127 modCache: modCache, 128 processEnv: env, 129 } 130 s.refreshTimer = newRefreshTimer(s.refreshProcessEnv) 131 return s 132 } 133 134 // runProcessEnvFunc runs goimports. 135 // 136 // Any call to runProcessEnvFunc will schedule a refresh of the imports state 137 // at some point in the future, if such a refresh is not already scheduled. See 138 // [refreshTimer] for more details. 139 func (s *importsState) runProcessEnvFunc(ctx context.Context, snapshot *Snapshot, fn func(context.Context, *imports.Options) error) error { 140 ctx, done := event.Start(ctx, "cache.importsState.runProcessEnvFunc") 141 defer done() 142 143 s.mu.Lock() 144 defer s.mu.Unlock() 145 146 // Find the hash of active mod files, if any. Using the unsaved content 147 // is slightly wasteful, since we'll drop caches a little too often, but 148 // the mod file shouldn't be changing while people are autocompleting. 149 // 150 // TODO(rfindley): consider instead hashing on-disk modfiles here. 151 var modFileHash file.Hash 152 for m := range snapshot.view.workspaceModFiles { 153 fh, err := snapshot.ReadFile(ctx, m) 154 if err != nil { 155 return err 156 } 157 modFileHash.XORWith(fh.Identity().Hash) 158 } 159 160 // If anything relevant to imports has changed, clear caches and 161 // update the processEnv. Clearing caches blocks on any background 162 // scans. 163 if modFileHash != s.cachedModFileHash { 164 s.processEnv.ClearModuleInfo() 165 s.cachedModFileHash = modFileHash 166 } 167 168 // Run the user function. 169 opts := &imports.Options{ 170 // Defaults. 171 AllErrors: true, 172 Comments: true, 173 Fragment: true, 174 FormatOnly: false, 175 TabIndent: true, 176 TabWidth: 8, 177 Env: s.processEnv, 178 LocalPrefix: snapshot.Options().Local, 179 } 180 181 if err := fn(ctx, opts); err != nil { 182 return err 183 } 184 185 // Refresh the imports resolver after usage. This may seem counterintuitive, 186 // since it means the first ProcessEnvFunc after a long period of inactivity 187 // may be stale, but in practice we run ProcessEnvFuncs frequently during 188 // active development (e.g. during completion), and so this mechanism will be 189 // active while gopls is in use, and inactive when gopls is idle. 190 s.refreshTimer.schedule() 191 192 // TODO(rfindley): the GOMODCACHE value used here isn't directly tied to the 193 // ProcessEnv.Env["GOMODCACHE"], though they should theoretically always 194 // agree. It would be better if we guaranteed this, possibly by setting all 195 // required environment variables in ProcessEnv.Env, to avoid the redundant 196 // Go command invocation. 197 gomodcache := snapshot.view.folder.Env.GOMODCACHE 198 s.modCache.refreshDir(s.ctx, gomodcache, s.processEnv.Logf) 199 200 return nil 201 } 202 203 func (s *importsState) refreshProcessEnv() { 204 ctx, done := event.Start(s.ctx, "cache.importsState.refreshProcessEnv") 205 defer done() 206 207 start := time.Now() 208 209 s.mu.Lock() 210 resolver, err := s.processEnv.GetResolver() 211 s.mu.Unlock() 212 if err != nil { 213 return 214 } 215 216 event.Log(s.ctx, "background imports cache refresh starting") 217 218 // Prime the new resolver before updating the processEnv, so that gopls 219 // doesn't wait on an unprimed cache. 220 if err := imports.PrimeCache(context.Background(), resolver); err == nil { 221 event.Log(ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start))) 222 } else { 223 event.Log(ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start)), keys.Err.Of(err)) 224 } 225 226 s.mu.Lock() 227 s.processEnv.UpdateResolver(resolver) 228 s.mu.Unlock() 229 }