mirror of
https://github.com/golang/go
synced 2024-11-18 18:44:42 -07:00
internal/lsp/cache: refresh imports cache in the background
Now that we can detach scans, it's easy to kick off a background refresh that doesn't block the user. Performance may be a bit worse until the scan finishes, but that's the price we pay for freshness. Adaptively rate-limit the refresh rate so that we don't turn the user's computer into a hot plate. Change-Id: Icbe8f384f44a219b57465da22d9becc19001eab8 Reviewed-on: https://go-review.googlesource.com/c/tools/+/212022 Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
872a348c38
commit
1c4842a210
@ -643,6 +643,22 @@ func getCandidatePkgs(ctx context.Context, wrappedCallback *scanCallback, filena
|
||||
return env.GetResolver().scan(ctx, scanFilter)
|
||||
}
|
||||
|
||||
func PrimeCache(ctx context.Context, env *ProcessEnv) error {
|
||||
// Fully scan the disk for directories, but don't actually read any Go files.
|
||||
callback := &scanCallback{
|
||||
rootFound: func(gopathwalk.Root) bool {
|
||||
return true
|
||||
},
|
||||
dirFound: func(pkg *pkg) bool {
|
||||
return false
|
||||
},
|
||||
packageNameLoaded: func(pkg *pkg) bool {
|
||||
return false
|
||||
},
|
||||
}
|
||||
return getCandidatePkgs(ctx, callback, "", "", env)
|
||||
}
|
||||
|
||||
func candidateImportName(pkg *pkg) string {
|
||||
if ImportPathToAssumedName(pkg.importPathShort) != pkg.packageName {
|
||||
return pkg.packageName
|
||||
@ -737,6 +753,13 @@ type ProcessEnv struct {
|
||||
resolver Resolver
|
||||
}
|
||||
|
||||
// CopyConfig copies the env's configuration into a new env.
|
||||
func (e *ProcessEnv) CopyConfig() *ProcessEnv {
|
||||
copy := *e
|
||||
copy.resolver = nil
|
||||
return ©
|
||||
}
|
||||
|
||||
func (e *ProcessEnv) env() []string {
|
||||
env := os.Environ()
|
||||
add := func(k, v string) {
|
||||
|
@ -1660,8 +1660,7 @@ func (t *goimportTest) processNonModule(file string, contents []byte, opts *Opti
|
||||
opts = &Options{Comments: true, TabIndent: true, TabWidth: 8}
|
||||
}
|
||||
// ProcessEnv is not safe for concurrent use. Make a copy.
|
||||
env := *t.env
|
||||
opts.Env = &env
|
||||
opts.Env = t.env.CopyConfig()
|
||||
return Process(file, contents, opts)
|
||||
}
|
||||
|
||||
|
49
internal/lsp/cache/view.go
vendored
49
internal/lsp/cache/view.go
vendored
@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
stdlog "log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
@ -25,7 +26,6 @@ import (
|
||||
"golang.org/x/tools/internal/memoize"
|
||||
"golang.org/x/tools/internal/span"
|
||||
"golang.org/x/tools/internal/telemetry/log"
|
||||
"golang.org/x/tools/internal/telemetry/tag"
|
||||
"golang.org/x/tools/internal/xcontext"
|
||||
errors "golang.org/x/xerrors"
|
||||
)
|
||||
@ -66,8 +66,9 @@ type view struct {
|
||||
// TODO(suzmue): the state cached in the process env is specific to each view,
|
||||
// however, there is state that can be shared between views that is not currently
|
||||
// cached, like the module cache.
|
||||
processEnv *imports.ProcessEnv
|
||||
cacheRefreshTime time.Time
|
||||
processEnv *imports.ProcessEnv
|
||||
cacheRefreshDuration time.Duration
|
||||
cacheRefreshTimer *time.Timer
|
||||
|
||||
// modFileVersions stores the last seen versions of the module files that are used
|
||||
// by processEnvs resolver.
|
||||
@ -306,29 +307,43 @@ func (v *view) RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options)
|
||||
if err := fn(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
if v.cacheRefreshTime.IsZero() {
|
||||
v.cacheRefreshTime = time.Now()
|
||||
}
|
||||
|
||||
// If applicable, store the file versions of the 'go.mod' files that are
|
||||
// looked at by the resolver.
|
||||
v.storeModFileVersions()
|
||||
|
||||
if time.Since(v.cacheRefreshTime) > 30*time.Second {
|
||||
go func() {
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
|
||||
log.Print(context.Background(), "background imports cache refresh starting")
|
||||
v.processEnv.GetResolver().ClearForNewScan()
|
||||
// TODO(heschi): prime the cache
|
||||
v.cacheRefreshTime = time.Now()
|
||||
log.Print(context.Background(), "background refresh finished with err: ", tag.Of("err", nil))
|
||||
}()
|
||||
if v.cacheRefreshTimer == nil {
|
||||
// Don't refresh more than twice per minute.
|
||||
delay := 30 * time.Second
|
||||
// Don't spend more than a couple percent of the time refreshing.
|
||||
if adaptive := 50 * v.cacheRefreshDuration; adaptive > delay {
|
||||
delay = adaptive
|
||||
}
|
||||
v.cacheRefreshTimer = time.AfterFunc(delay, v.refreshProcessEnv)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *view) refreshProcessEnv() {
|
||||
start := time.Now()
|
||||
|
||||
v.mu.Lock()
|
||||
env := v.processEnv
|
||||
env.GetResolver().ClearForNewScan()
|
||||
v.mu.Unlock()
|
||||
|
||||
// We don't have a context handy to use for logging, so use the stdlib for now.
|
||||
stdlog.Printf("background imports cache refresh starting")
|
||||
err := imports.PrimeCache(context.Background(), env)
|
||||
stdlog.Printf("background refresh finished after %v with err: %v", time.Since(start), err)
|
||||
|
||||
v.mu.Lock()
|
||||
v.cacheRefreshDuration = time.Since(start)
|
||||
v.cacheRefreshTimer = nil
|
||||
v.mu.Unlock()
|
||||
}
|
||||
|
||||
func (v *view) buildProcessEnv(ctx context.Context) (*imports.ProcessEnv, error) {
|
||||
cfg := v.Config(ctx)
|
||||
env := &imports.ProcessEnv{
|
||||
|
Loading…
Reference in New Issue
Block a user