cuelang.org/go@v0.13.0/internal/golangorgx/gopls/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 "cuelang.org/go/internal/golangorgx/gopls/file" 14 "cuelang.org/go/internal/golangorgx/tools/event" 15 "cuelang.org/go/internal/golangorgx/tools/event/keys" 16 "cuelang.org/go/internal/golangorgx/tools/event/tag" 17 "cuelang.org/go/internal/golangorgx/tools/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 func (s *importsState) refreshProcessEnv() { 135 ctx, done := event.Start(s.ctx, "cache.importsState.refreshProcessEnv") 136 defer done() 137 138 start := time.Now() 139 140 s.mu.Lock() 141 resolver, err := s.processEnv.GetResolver() 142 s.mu.Unlock() 143 if err != nil { 144 return 145 } 146 147 event.Log(s.ctx, "background imports cache refresh starting") 148 149 // Prime the new resolver before updating the processEnv, so that gopls 150 // doesn't wait on an unprimed cache. 151 if err := imports.PrimeCache(context.Background(), resolver); err == nil { 152 event.Log(ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start))) 153 } else { 154 event.Log(ctx, fmt.Sprintf("background refresh finished after %v", time.Since(start)), keys.Err.Of(err)) 155 } 156 157 s.mu.Lock() 158 s.processEnv.UpdateResolver(resolver) 159 s.mu.Unlock() 160 }