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  }