2019-05-15 10:24:49 -06:00
|
|
|
|
// Copyright 2019 The Go Authors. All rights reserved.
|
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
package cache
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2020-07-19 13:11:07 -06:00
|
|
|
|
"fmt"
|
2019-05-29 12:55:52 -06:00
|
|
|
|
"strconv"
|
2019-05-15 10:24:49 -06:00
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
2019-05-29 12:55:52 -06:00
|
|
|
|
"sync/atomic"
|
2019-05-15 10:24:49 -06:00
|
|
|
|
|
2020-04-17 07:32:56 -06:00
|
|
|
|
"golang.org/x/tools/internal/event"
|
2020-03-23 06:35:36 -06:00
|
|
|
|
"golang.org/x/tools/internal/gocommand"
|
2020-07-07 13:18:31 -06:00
|
|
|
|
"golang.org/x/tools/internal/imports"
|
2019-05-15 10:24:49 -06:00
|
|
|
|
"golang.org/x/tools/internal/lsp/source"
|
|
|
|
|
"golang.org/x/tools/internal/span"
|
2019-07-10 19:11:23 -06:00
|
|
|
|
"golang.org/x/tools/internal/xcontext"
|
2019-08-06 13:13:11 -06:00
|
|
|
|
errors "golang.org/x/xerrors"
|
2019-05-15 10:24:49 -06:00
|
|
|
|
)
|
|
|
|
|
|
2020-02-18 18:59:37 -07:00
|
|
|
|
type Session struct {
|
|
|
|
|
cache *Cache
|
2019-05-29 12:55:52 -06:00
|
|
|
|
id string
|
2019-05-15 10:24:49 -06:00
|
|
|
|
|
2019-09-11 11:13:44 -06:00
|
|
|
|
options source.Options
|
2019-09-05 22:17:36 -06:00
|
|
|
|
|
2019-05-15 10:24:49 -06:00
|
|
|
|
viewMu sync.Mutex
|
2020-06-02 08:57:20 -06:00
|
|
|
|
views []*View
|
|
|
|
|
viewMap map[span.URI]*View
|
2019-05-17 08:51:19 -06:00
|
|
|
|
|
|
|
|
|
overlayMu sync.Mutex
|
2019-05-31 17:41:39 -06:00
|
|
|
|
overlays map[span.URI]*overlay
|
2020-06-25 23:34:55 -06:00
|
|
|
|
|
|
|
|
|
// gocmdRunner guards go command calls from concurrency errors.
|
|
|
|
|
gocmdRunner *gocommand.Runner
|
2019-05-15 10:24:49 -06:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 14:20:50 -07:00
|
|
|
|
type overlay struct {
|
2020-02-18 18:59:37 -07:00
|
|
|
|
session *Session
|
2020-02-06 14:20:50 -07:00
|
|
|
|
uri span.URI
|
|
|
|
|
text []byte
|
|
|
|
|
hash string
|
|
|
|
|
version float64
|
|
|
|
|
kind source.FileKind
|
|
|
|
|
|
|
|
|
|
// saved is true if a file has been saved on disk,
|
|
|
|
|
// and therefore does not need to be part of the overlay sent to go/packages.
|
|
|
|
|
saved bool
|
|
|
|
|
}
|
|
|
|
|
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
|
func (o *overlay) Read() ([]byte, error) {
|
|
|
|
|
return o.text, nil
|
2020-02-06 14:20:50 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (o *overlay) Identity() source.FileIdentity {
|
|
|
|
|
return source.FileIdentity{
|
|
|
|
|
URI: o.uri,
|
|
|
|
|
Identifier: o.hash,
|
2020-03-04 11:55:41 -07:00
|
|
|
|
SessionID: o.session.id,
|
2020-02-06 14:20:50 -07:00
|
|
|
|
Version: o.version,
|
|
|
|
|
Kind: o.kind,
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-06-02 08:57:20 -06:00
|
|
|
|
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
|
func (o *overlay) Kind() source.FileKind {
|
|
|
|
|
return o.kind
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (o *overlay) URI() span.URI {
|
|
|
|
|
return o.uri
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (o *overlay) Version() float64 {
|
|
|
|
|
return o.version
|
2020-02-06 14:20:50 -07:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-02 08:57:20 -06:00
|
|
|
|
func (o *overlay) Session() source.Session { return o.session }
|
|
|
|
|
func (o *overlay) Saved() bool { return o.saved }
|
|
|
|
|
func (o *overlay) Data() []byte { return o.text }
|
|
|
|
|
|
2020-06-03 15:06:45 -06:00
|
|
|
|
func (s *Session) ID() string { return s.id }
|
|
|
|
|
func (s *Session) String() string { return s.id }
|
2020-06-02 08:57:20 -06:00
|
|
|
|
|
2020-02-18 18:59:37 -07:00
|
|
|
|
func (s *Session) Options() source.Options {
|
2019-09-05 22:17:36 -06:00
|
|
|
|
return s.options
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-18 18:59:37 -07:00
|
|
|
|
func (s *Session) SetOptions(options source.Options) {
|
2019-09-05 22:17:36 -06:00
|
|
|
|
s.options = options
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-18 18:59:37 -07:00
|
|
|
|
func (s *Session) Shutdown(ctx context.Context) {
|
2019-05-15 10:24:49 -06:00
|
|
|
|
s.viewMu.Lock()
|
|
|
|
|
defer s.viewMu.Unlock()
|
|
|
|
|
for _, view := range s.views {
|
|
|
|
|
view.shutdown(ctx)
|
|
|
|
|
}
|
|
|
|
|
s.views = nil
|
|
|
|
|
s.viewMap = nil
|
2020-06-03 15:06:45 -06:00
|
|
|
|
event.Log(ctx, "Shutdown session", KeyShutdownSession.Of(s))
|
2019-05-15 10:24:49 -06:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-18 18:59:37 -07:00
|
|
|
|
func (s *Session) Cache() source.Cache {
|
2019-05-15 10:24:49 -06:00
|
|
|
|
return s.cache
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-18 18:59:37 -07:00
|
|
|
|
func (s *Session) NewView(ctx context.Context, name string, folder span.URI, options source.Options) (source.View, source.Snapshot, error) {
|
2019-05-15 10:24:49 -06:00
|
|
|
|
s.viewMu.Lock()
|
|
|
|
|
defer s.viewMu.Unlock()
|
2020-03-27 11:11:41 -06:00
|
|
|
|
v, snapshot, err := s.createView(ctx, name, folder, options, 0)
|
2019-11-08 11:25:29 -07:00
|
|
|
|
if err != nil {
|
2019-11-15 12:47:29 -07:00
|
|
|
|
return nil, nil, err
|
2019-11-08 11:25:29 -07:00
|
|
|
|
}
|
|
|
|
|
s.views = append(s.views, v)
|
|
|
|
|
// we always need to drop the view map
|
2020-06-02 08:57:20 -06:00
|
|
|
|
s.viewMap = make(map[span.URI]*View)
|
2019-11-29 21:51:14 -07:00
|
|
|
|
return v, snapshot, nil
|
2019-11-08 11:25:29 -07:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-02 08:57:20 -06:00
|
|
|
|
func (s *Session) createView(ctx context.Context, name string, folder span.URI, options source.Options, snapshotID uint64) (*View, *snapshot, error) {
|
2019-11-08 11:25:29 -07:00
|
|
|
|
index := atomic.AddInt64(&viewIndex, 1)
|
2019-09-09 11:04:12 -06:00
|
|
|
|
// We want a true background context and not a detached context here
|
2019-07-16 20:20:43 -06:00
|
|
|
|
// the spans need to be unrelated and no tag values should pollute it.
|
2020-03-07 19:28:21 -07:00
|
|
|
|
baseCtx := event.Detach(xcontext.Detach(ctx))
|
2019-07-16 20:20:43 -06:00
|
|
|
|
backgroundCtx, cancel := context.WithCancel(baseCtx)
|
2019-12-16 13:40:24 -07:00
|
|
|
|
|
2020-06-02 08:57:20 -06:00
|
|
|
|
v := &View{
|
internal/lsp: handle unknown revision in go.mod file
This change ensures that, when the initial workspace load fails, we
re-run it if the go.mod file changes. Previously, if a user opened a
workspace with a corrupt go.mod file, we never recovered.
To reinitialize the workspace on-demand, we use the initializeOnce field
as an indicator of whether or not we should reinitialize. Every call to
awaitInitialized (which is called by all functions that need the IWL),
passes through the initialization code. If a retry isn't necessary,
this is a no-op, but if it is, we will call the initialization logic.
Only the first attempt uses a detached context; subsequent attempts can
be canceled by their contexts.
To indicate that we should reinitialize, we call maybeReinitialize.
Right now, we only call this when the go.mod file changes. In the
future, we may need it in other cases.
Fixes golang/go#38232
Change-Id: I77eefebb0ac38fbd0fe2c7da09c864eba45b075f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/242159
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-07-11 23:52:22 -06:00
|
|
|
|
session: s,
|
|
|
|
|
initialized: make(chan struct{}),
|
|
|
|
|
initializationSema: make(chan struct{}, 1),
|
|
|
|
|
initializeOnce: &sync.Once{},
|
|
|
|
|
id: strconv.FormatInt(index, 10),
|
|
|
|
|
options: options,
|
|
|
|
|
baseCtx: baseCtx,
|
|
|
|
|
backgroundCtx: backgroundCtx,
|
|
|
|
|
cancel: cancel,
|
|
|
|
|
name: name,
|
|
|
|
|
folder: folder,
|
|
|
|
|
filesByURI: make(map[span.URI]*fileBase),
|
|
|
|
|
filesByBase: make(map[string][]*fileBase),
|
2019-09-23 18:06:15 -06:00
|
|
|
|
snapshot: &snapshot{
|
2020-03-27 11:11:41 -06:00
|
|
|
|
id: snapshotID,
|
2019-11-29 23:17:57 -07:00
|
|
|
|
packages: make(map[packageKey]*packageHandle),
|
2019-11-12 18:16:00 -07:00
|
|
|
|
ids: make(map[span.URI][]packageID),
|
|
|
|
|
metadata: make(map[packageID]*metadata),
|
|
|
|
|
files: make(map[span.URI]source.FileHandle),
|
|
|
|
|
importedBy: make(map[packageID][]packageID),
|
|
|
|
|
actions: make(map[actionKey]*actionHandle),
|
2020-01-07 19:37:41 -07:00
|
|
|
|
workspacePackages: make(map[packageID]packagePath),
|
2020-01-23 17:24:51 -07:00
|
|
|
|
unloadableFiles: make(map[span.URI]struct{}),
|
2020-06-19 17:07:57 -06:00
|
|
|
|
parseModHandles: make(map[span.URI]*parseModHandle),
|
2019-05-15 10:24:49 -06:00
|
|
|
|
},
|
2019-05-15 15:58:16 -06:00
|
|
|
|
}
|
2019-09-27 11:17:59 -06:00
|
|
|
|
v.snapshot.view = v
|
2019-10-01 13:21:06 -06:00
|
|
|
|
|
2019-10-10 18:48:16 -06:00
|
|
|
|
if v.session.cache.options != nil {
|
|
|
|
|
v.session.cache.options(&v.options)
|
|
|
|
|
}
|
2020-01-11 18:29:13 -07:00
|
|
|
|
// Set the module-specific information.
|
2020-01-23 23:22:47 -07:00
|
|
|
|
if err := v.setBuildInformation(ctx, folder, options.Env, v.options.TempModfile); err != nil {
|
2020-01-09 23:45:57 -07:00
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-07 13:18:31 -06:00
|
|
|
|
// We have v.goEnv now.
|
|
|
|
|
v.processEnv = &imports.ProcessEnv{
|
|
|
|
|
GocmdRunner: s.gocmdRunner,
|
|
|
|
|
WorkingDir: folder.Filename(),
|
|
|
|
|
Env: v.goEnv,
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-09 23:45:57 -07:00
|
|
|
|
// Initialize the view without blocking.
|
2020-06-11 22:24:37 -06:00
|
|
|
|
initCtx, initCancel := context.WithCancel(xcontext.Detach(ctx))
|
internal/lsp: handle unknown revision in go.mod file
This change ensures that, when the initial workspace load fails, we
re-run it if the go.mod file changes. Previously, if a user opened a
workspace with a corrupt go.mod file, we never recovered.
To reinitialize the workspace on-demand, we use the initializeOnce field
as an indicator of whether or not we should reinitialize. Every call to
awaitInitialized (which is called by all functions that need the IWL),
passes through the initialization code. If a retry isn't necessary,
this is a no-op, but if it is, we will call the initialization logic.
Only the first attempt uses a detached context; subsequent attempts can
be canceled by their contexts.
To indicate that we should reinitialize, we call maybeReinitialize.
Right now, we only call this when the go.mod file changes. In the
future, we may need it in other cases.
Fixes golang/go#38232
Change-Id: I77eefebb0ac38fbd0fe2c7da09c864eba45b075f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/242159
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-07-11 23:52:22 -06:00
|
|
|
|
v.initCancelFirstAttempt = initCancel
|
|
|
|
|
go v.initialize(initCtx, v.snapshot, true)
|
2019-11-29 21:51:14 -07:00
|
|
|
|
return v, v.snapshot, nil
|
2019-05-15 10:24:49 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// View returns the view by name.
|
2020-02-18 18:59:37 -07:00
|
|
|
|
func (s *Session) View(name string) source.View {
|
2019-05-15 10:24:49 -06:00
|
|
|
|
s.viewMu.Lock()
|
|
|
|
|
defer s.viewMu.Unlock()
|
|
|
|
|
for _, view := range s.views {
|
|
|
|
|
if view.Name() == name {
|
|
|
|
|
return view
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ViewOf returns a view corresponding to the given URI.
|
|
|
|
|
// If the file is not already associated with a view, pick one using some heuristics.
|
2020-02-18 18:59:37 -07:00
|
|
|
|
func (s *Session) ViewOf(uri span.URI) (source.View, error) {
|
2019-12-10 10:29:37 -07:00
|
|
|
|
return s.viewOf(uri)
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-02 08:57:20 -06:00
|
|
|
|
func (s *Session) viewOf(uri span.URI) (*View, error) {
|
2019-05-15 10:24:49 -06:00
|
|
|
|
s.viewMu.Lock()
|
|
|
|
|
defer s.viewMu.Unlock()
|
|
|
|
|
|
2019-05-29 12:37:11 -06:00
|
|
|
|
// Check if we already know this file.
|
2019-05-15 10:24:49 -06:00
|
|
|
|
if v, found := s.viewMap[uri]; found {
|
2019-11-15 10:43:45 -07:00
|
|
|
|
return v, nil
|
2019-05-15 10:24:49 -06:00
|
|
|
|
}
|
2019-05-29 12:37:11 -06:00
|
|
|
|
// Pick the best view for this file and memoize the result.
|
2019-11-15 10:43:45 -07:00
|
|
|
|
v, err := s.bestView(uri)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2019-05-15 10:24:49 -06:00
|
|
|
|
s.viewMap[uri] = v
|
2019-11-15 10:43:45 -07:00
|
|
|
|
return v, nil
|
2019-05-15 10:24:49 -06:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-02 08:57:20 -06:00
|
|
|
|
func (s *Session) viewsOf(uri span.URI) []*View {
|
2019-09-23 18:06:15 -06:00
|
|
|
|
s.viewMu.Lock()
|
|
|
|
|
defer s.viewMu.Unlock()
|
|
|
|
|
|
2020-06-02 08:57:20 -06:00
|
|
|
|
var views []*View
|
2019-09-23 18:06:15 -06:00
|
|
|
|
for _, view := range s.views {
|
|
|
|
|
if strings.HasPrefix(string(uri), string(view.Folder())) {
|
|
|
|
|
views = append(views, view)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return views
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-18 18:59:37 -07:00
|
|
|
|
func (s *Session) Views() []source.View {
|
2019-05-15 10:24:49 -06:00
|
|
|
|
s.viewMu.Lock()
|
|
|
|
|
defer s.viewMu.Unlock()
|
|
|
|
|
result := make([]source.View, len(s.views))
|
|
|
|
|
for i, v := range s.views {
|
|
|
|
|
result[i] = v
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// bestView finds the best view to associate a given URI with.
|
|
|
|
|
// viewMu must be held when calling this method.
|
2020-06-02 08:57:20 -06:00
|
|
|
|
func (s *Session) bestView(uri span.URI) (*View, error) {
|
2019-11-15 10:43:45 -07:00
|
|
|
|
if len(s.views) == 0 {
|
|
|
|
|
return nil, errors.Errorf("no views in the session")
|
|
|
|
|
}
|
2019-05-15 10:24:49 -06:00
|
|
|
|
// we need to find the best view for this file
|
2020-06-02 08:57:20 -06:00
|
|
|
|
var longest *View
|
2019-05-15 10:24:49 -06:00
|
|
|
|
for _, view := range s.views {
|
|
|
|
|
if longest != nil && len(longest.Folder()) > len(view.Folder()) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2020-01-30 18:52:23 -07:00
|
|
|
|
if view.contains(uri) {
|
2019-05-15 10:24:49 -06:00
|
|
|
|
longest = view
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if longest != nil {
|
2019-11-15 10:43:45 -07:00
|
|
|
|
return longest, nil
|
2019-05-15 10:24:49 -06:00
|
|
|
|
}
|
2020-06-25 23:34:55 -06:00
|
|
|
|
// Try our best to return a view that knows the file.
|
|
|
|
|
for _, view := range s.views {
|
|
|
|
|
if view.knownFile(uri) {
|
|
|
|
|
return view, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-29 12:37:11 -06:00
|
|
|
|
// TODO: are there any more heuristics we can use?
|
2019-11-15 10:43:45 -07:00
|
|
|
|
return s.views[0], nil
|
2019-05-15 10:24:49 -06:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-02 08:57:20 -06:00
|
|
|
|
func (s *Session) removeView(ctx context.Context, view *View) error {
|
2019-05-15 10:24:49 -06:00
|
|
|
|
s.viewMu.Lock()
|
|
|
|
|
defer s.viewMu.Unlock()
|
2019-11-08 11:25:29 -07:00
|
|
|
|
i, err := s.dropView(ctx, view)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
// delete this view... we don't care about order but we do want to make
|
|
|
|
|
// sure we can garbage collect the view
|
|
|
|
|
s.views[i] = s.views[len(s.views)-1]
|
|
|
|
|
s.views[len(s.views)-1] = nil
|
|
|
|
|
s.views = s.views[:len(s.views)-1]
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-02 08:57:20 -06:00
|
|
|
|
func (s *Session) updateView(ctx context.Context, view *View, options source.Options) (*View, *snapshot, error) {
|
2019-11-08 11:25:29 -07:00
|
|
|
|
s.viewMu.Lock()
|
|
|
|
|
defer s.viewMu.Unlock()
|
|
|
|
|
i, err := s.dropView(ctx, view)
|
|
|
|
|
if err != nil {
|
2019-11-15 12:47:29 -07:00
|
|
|
|
return nil, nil, err
|
2019-11-08 11:25:29 -07:00
|
|
|
|
}
|
2020-03-27 11:11:41 -06:00
|
|
|
|
// Preserve the snapshot ID if we are recreating the view.
|
|
|
|
|
view.snapshotMu.Lock()
|
|
|
|
|
snapshotID := view.snapshot.id
|
|
|
|
|
view.snapshotMu.Unlock()
|
|
|
|
|
v, snapshot, err := s.createView(ctx, view.name, view.folder, options, snapshotID)
|
2019-11-08 11:25:29 -07:00
|
|
|
|
if err != nil {
|
|
|
|
|
// we have dropped the old view, but could not create the new one
|
|
|
|
|
// this should not happen and is very bad, but we still need to clean
|
|
|
|
|
// up the view array if it happens
|
|
|
|
|
s.views[i] = s.views[len(s.views)-1]
|
|
|
|
|
s.views[len(s.views)-1] = nil
|
|
|
|
|
s.views = s.views[:len(s.views)-1]
|
|
|
|
|
}
|
|
|
|
|
// substitute the new view into the array where the old view was
|
|
|
|
|
s.views[i] = v
|
2019-11-29 21:51:14 -07:00
|
|
|
|
return v, snapshot, nil
|
2019-11-08 11:25:29 -07:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-02 08:57:20 -06:00
|
|
|
|
func (s *Session) dropView(ctx context.Context, v *View) (int, error) {
|
2019-05-15 10:24:49 -06:00
|
|
|
|
// we always need to drop the view map
|
2020-06-02 08:57:20 -06:00
|
|
|
|
s.viewMap = make(map[span.URI]*View)
|
2019-12-10 10:29:37 -07:00
|
|
|
|
for i := range s.views {
|
|
|
|
|
if v == s.views[i] {
|
2019-11-08 11:25:29 -07:00
|
|
|
|
// we found the view, drop it and return the index it was found at
|
|
|
|
|
s.views[i] = nil
|
2019-05-15 10:24:49 -06:00
|
|
|
|
v.shutdown(ctx)
|
2019-11-08 11:25:29 -07:00
|
|
|
|
return i, nil
|
2019-05-15 10:24:49 -06:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-12-10 10:29:37 -07:00
|
|
|
|
return -1, errors.Errorf("view %s for %v not found", v.Name(), v.Folder())
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-18 18:59:37 -07:00
|
|
|
|
func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModification) ([]source.Snapshot, error) {
|
2020-06-02 08:57:20 -06:00
|
|
|
|
views := make(map[*View]map[span.URI]source.FileHandle)
|
2019-12-10 10:29:37 -07:00
|
|
|
|
|
2020-02-06 14:20:50 -07:00
|
|
|
|
overlays, err := s.updateOverlays(ctx, changes)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
internal/lsp: handle unknown revision in go.mod file
This change ensures that, when the initial workspace load fails, we
re-run it if the go.mod file changes. Previously, if a user opened a
workspace with a corrupt go.mod file, we never recovered.
To reinitialize the workspace on-demand, we use the initializeOnce field
as an indicator of whether or not we should reinitialize. Every call to
awaitInitialized (which is called by all functions that need the IWL),
passes through the initialization code. If a retry isn't necessary,
this is a no-op, but if it is, we will call the initialization logic.
Only the first attempt uses a detached context; subsequent attempts can
be canceled by their contexts.
To indicate that we should reinitialize, we call maybeReinitialize.
Right now, we only call this when the go.mod file changes. In the
future, we may need it in other cases.
Fixes golang/go#38232
Change-Id: I77eefebb0ac38fbd0fe2c7da09c864eba45b075f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/242159
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-07-11 23:52:22 -06:00
|
|
|
|
var forceReloadMetadata bool
|
2020-01-22 19:34:21 -07:00
|
|
|
|
for _, c := range changes {
|
2020-05-14 12:02:48 -06:00
|
|
|
|
if c.Action == source.InvalidateMetadata {
|
|
|
|
|
forceReloadMetadata = true
|
|
|
|
|
}
|
2020-02-10 12:45:18 -07:00
|
|
|
|
// Look through all of the session's views, invalidating the file for
|
|
|
|
|
// all of the views to which it is known.
|
|
|
|
|
for _, view := range s.views {
|
|
|
|
|
// Don't propagate changes that are outside of the view's scope
|
|
|
|
|
// or knowledge.
|
|
|
|
|
if !view.relevantChange(c) {
|
|
|
|
|
continue
|
2020-01-09 20:45:06 -07:00
|
|
|
|
}
|
2020-01-22 19:34:21 -07:00
|
|
|
|
// Make sure that the file is added to the view.
|
|
|
|
|
if _, err := view.getFile(c.URI); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2020-02-06 14:20:50 -07:00
|
|
|
|
if _, ok := views[view]; !ok {
|
|
|
|
|
views[view] = make(map[span.URI]source.FileHandle)
|
|
|
|
|
}
|
internal/lsp: handle unknown revision in go.mod file
This change ensures that, when the initial workspace load fails, we
re-run it if the go.mod file changes. Previously, if a user opened a
workspace with a corrupt go.mod file, we never recovered.
To reinitialize the workspace on-demand, we use the initializeOnce field
as an indicator of whether or not we should reinitialize. Every call to
awaitInitialized (which is called by all functions that need the IWL),
passes through the initialization code. If a retry isn't necessary,
this is a no-op, but if it is, we will call the initialization logic.
Only the first attempt uses a detached context; subsequent attempts can
be canceled by their contexts.
To indicate that we should reinitialize, we call maybeReinitialize.
Right now, we only call this when the go.mod file changes. In the
future, we may need it in other cases.
Fixes golang/go#38232
Change-Id: I77eefebb0ac38fbd0fe2c7da09c864eba45b075f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/242159
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-07-11 23:52:22 -06:00
|
|
|
|
var (
|
|
|
|
|
fh source.FileHandle
|
|
|
|
|
ok bool
|
|
|
|
|
err error
|
|
|
|
|
)
|
|
|
|
|
if fh, ok = overlays[c.URI]; ok {
|
|
|
|
|
views[view][c.URI] = fh
|
2020-02-06 14:20:50 -07:00
|
|
|
|
} else {
|
internal/lsp: handle unknown revision in go.mod file
This change ensures that, when the initial workspace load fails, we
re-run it if the go.mod file changes. Previously, if a user opened a
workspace with a corrupt go.mod file, we never recovered.
To reinitialize the workspace on-demand, we use the initializeOnce field
as an indicator of whether or not we should reinitialize. Every call to
awaitInitialized (which is called by all functions that need the IWL),
passes through the initialization code. If a retry isn't necessary,
this is a no-op, but if it is, we will call the initialization logic.
Only the first attempt uses a detached context; subsequent attempts can
be canceled by their contexts.
To indicate that we should reinitialize, we call maybeReinitialize.
Right now, we only call this when the go.mod file changes. In the
future, we may need it in other cases.
Fixes golang/go#38232
Change-Id: I77eefebb0ac38fbd0fe2c7da09c864eba45b075f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/242159
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-07-11 23:52:22 -06:00
|
|
|
|
fh, err = s.cache.getFile(ctx, c.URI)
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
views[view][c.URI] = fh
|
2020-02-06 14:20:50 -07:00
|
|
|
|
}
|
internal/lsp: handle unknown revision in go.mod file
This change ensures that, when the initial workspace load fails, we
re-run it if the go.mod file changes. Previously, if a user opened a
workspace with a corrupt go.mod file, we never recovered.
To reinitialize the workspace on-demand, we use the initializeOnce field
as an indicator of whether or not we should reinitialize. Every call to
awaitInitialized (which is called by all functions that need the IWL),
passes through the initialization code. If a retry isn't necessary,
this is a no-op, but if it is, we will call the initialization logic.
Only the first attempt uses a detached context; subsequent attempts can
be canceled by their contexts.
To indicate that we should reinitialize, we call maybeReinitialize.
Right now, we only call this when the go.mod file changes. In the
future, we may need it in other cases.
Fixes golang/go#38232
Change-Id: I77eefebb0ac38fbd0fe2c7da09c864eba45b075f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/242159
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-07-11 23:52:22 -06:00
|
|
|
|
// If the file change is to a go.mod file, and initialization for
|
|
|
|
|
// the view has previously failed, we should attempt to retry.
|
|
|
|
|
// TODO(rstambler): We can use unsaved contents with -modfile, so
|
|
|
|
|
// maybe we should do that and retry on any change?
|
|
|
|
|
if fh.Kind() == source.Mod && (c.OnDisk || c.Action == source.Save) {
|
|
|
|
|
view.maybeReinitialize()
|
|
|
|
|
}
|
2020-01-09 20:45:06 -07:00
|
|
|
|
}
|
2020-01-22 19:34:21 -07:00
|
|
|
|
}
|
|
|
|
|
var snapshots []source.Snapshot
|
|
|
|
|
for view, uris := range views {
|
2020-05-14 12:02:48 -06:00
|
|
|
|
snapshots = append(snapshots, view.invalidateContent(ctx, uris, forceReloadMetadata))
|
2019-06-07 09:34:41 -06:00
|
|
|
|
}
|
2019-12-17 14:53:57 -07:00
|
|
|
|
return snapshots, nil
|
2019-05-17 10:15:22 -06:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-18 18:59:37 -07:00
|
|
|
|
func (s *Session) isOpen(uri span.URI) bool {
|
2019-12-18 22:59:50 -07:00
|
|
|
|
s.overlayMu.Lock()
|
|
|
|
|
defer s.overlayMu.Unlock()
|
|
|
|
|
|
|
|
|
|
_, open := s.overlays[uri]
|
2019-05-17 10:15:22 -06:00
|
|
|
|
return open
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-18 18:59:37 -07:00
|
|
|
|
func (s *Session) updateOverlays(ctx context.Context, changes []source.FileModification) (map[span.URI]*overlay, error) {
|
2020-02-06 14:20:50 -07:00
|
|
|
|
s.overlayMu.Lock()
|
|
|
|
|
defer s.overlayMu.Unlock()
|
|
|
|
|
|
|
|
|
|
for _, c := range changes {
|
2020-07-19 13:11:07 -06:00
|
|
|
|
// Don't update overlays for metadata invalidations.
|
|
|
|
|
if c.Action == source.InvalidateMetadata {
|
2020-02-06 14:20:50 -07:00
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
o, ok := s.overlays[c.URI]
|
|
|
|
|
|
2020-07-19 13:11:07 -06:00
|
|
|
|
// If the file is not opened in an overlay and the change is on disk,
|
|
|
|
|
// there's no need to update an overlay. If there is an overlay, we
|
|
|
|
|
// may need to update the overlay's saved value.
|
|
|
|
|
if !ok && c.OnDisk {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-06 14:20:50 -07:00
|
|
|
|
// Determine the file kind on open, otherwise, assume it has been cached.
|
|
|
|
|
var kind source.FileKind
|
|
|
|
|
switch c.Action {
|
|
|
|
|
case source.Open:
|
|
|
|
|
kind = source.DetectLanguage(c.LanguageID, c.URI.Filename())
|
|
|
|
|
default:
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, errors.Errorf("updateOverlays: modifying unopened overlay %v", c.URI)
|
|
|
|
|
}
|
|
|
|
|
kind = o.kind
|
|
|
|
|
}
|
|
|
|
|
if kind == source.UnknownKind {
|
|
|
|
|
return nil, errors.Errorf("updateOverlays: unknown file kind for %s", c.URI)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Closing a file just deletes its overlay.
|
|
|
|
|
if c.Action == source.Close {
|
|
|
|
|
delete(s.overlays, c.URI)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-19 22:26:20 -06:00
|
|
|
|
// If the file is on disk, check if its content is the same as in the
|
2020-07-19 13:11:07 -06:00
|
|
|
|
// overlay. Saves and on-disk file changes don't come with the file's
|
|
|
|
|
// content.
|
2020-02-06 14:20:50 -07:00
|
|
|
|
text := c.Text
|
2020-07-19 13:11:07 -06:00
|
|
|
|
if text == nil && (c.Action == source.Save || c.OnDisk) {
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, fmt.Errorf("no known content for overlay for %s", c.Action)
|
|
|
|
|
}
|
2020-02-06 14:20:50 -07:00
|
|
|
|
text = o.text
|
|
|
|
|
}
|
2020-07-19 13:11:07 -06:00
|
|
|
|
// On-disk changes don't come with versions.
|
|
|
|
|
version := c.Version
|
|
|
|
|
if c.OnDisk {
|
|
|
|
|
version = o.version
|
|
|
|
|
}
|
2020-02-06 14:20:50 -07:00
|
|
|
|
hash := hashContents(text)
|
|
|
|
|
var sameContentOnDisk bool
|
|
|
|
|
switch c.Action {
|
2020-07-19 13:11:07 -06:00
|
|
|
|
case source.Delete:
|
|
|
|
|
// Do nothing. sameContentOnDisk should be false.
|
2020-02-06 14:20:50 -07:00
|
|
|
|
case source.Save:
|
|
|
|
|
// Make sure the version and content (if present) is the same.
|
2020-07-19 13:11:07 -06:00
|
|
|
|
if o.version != version {
|
2020-02-06 14:20:50 -07:00
|
|
|
|
return nil, errors.Errorf("updateOverlays: saving %s at version %v, currently at %v", c.URI, c.Version, o.version)
|
|
|
|
|
}
|
|
|
|
|
if c.Text != nil && o.hash != hash {
|
|
|
|
|
return nil, errors.Errorf("updateOverlays: overlay %s changed on save", c.URI)
|
|
|
|
|
}
|
|
|
|
|
sameContentOnDisk = true
|
2020-07-19 13:11:07 -06:00
|
|
|
|
default:
|
|
|
|
|
fh, err := s.cache.getFile(ctx, c.URI)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
_, readErr := fh.Read()
|
|
|
|
|
sameContentOnDisk = (readErr == nil && fh.Identity().Identifier == hash)
|
2020-02-06 14:20:50 -07:00
|
|
|
|
}
|
|
|
|
|
o = &overlay{
|
|
|
|
|
session: s,
|
|
|
|
|
uri: c.URI,
|
2020-07-19 13:11:07 -06:00
|
|
|
|
version: version,
|
2020-02-06 14:20:50 -07:00
|
|
|
|
text: text,
|
|
|
|
|
kind: kind,
|
|
|
|
|
hash: hash,
|
|
|
|
|
saved: sameContentOnDisk,
|
|
|
|
|
}
|
|
|
|
|
s.overlays[c.URI] = o
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-19 13:11:07 -06:00
|
|
|
|
// Get the overlays for each change while the session's overlay map is
|
|
|
|
|
// locked.
|
2020-02-06 14:20:50 -07:00
|
|
|
|
overlays := make(map[span.URI]*overlay)
|
|
|
|
|
for _, c := range changes {
|
2020-01-30 19:29:41 -07:00
|
|
|
|
if o, ok := s.overlays[c.URI]; ok {
|
|
|
|
|
overlays[c.URI] = o
|
|
|
|
|
}
|
2020-02-06 14:20:50 -07:00
|
|
|
|
}
|
|
|
|
|
return overlays, nil
|
|
|
|
|
}
|
|
|
|
|
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
|
func (s *Session) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
|
2019-05-29 12:37:11 -06:00
|
|
|
|
if overlay := s.readOverlay(uri); overlay != nil {
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
|
return overlay, nil
|
2019-05-17 10:15:22 -06:00
|
|
|
|
}
|
2019-05-29 12:37:11 -06:00
|
|
|
|
// Fall back to the cache-level file system.
|
2020-06-19 17:07:57 -06:00
|
|
|
|
return s.cache.getFile(ctx, uri)
|
2019-05-17 10:15:22 -06:00
|
|
|
|
}
|
2020-02-06 14:20:50 -07:00
|
|
|
|
|
2020-02-18 18:59:37 -07:00
|
|
|
|
func (s *Session) readOverlay(uri span.URI) *overlay {
|
2020-02-06 14:20:50 -07:00
|
|
|
|
s.overlayMu.Lock()
|
|
|
|
|
defer s.overlayMu.Unlock()
|
|
|
|
|
|
|
|
|
|
if overlay, ok := s.overlays[uri]; ok {
|
|
|
|
|
return overlay
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2020-05-07 11:22:13 -06:00
|
|
|
|
|
2020-06-02 08:57:20 -06:00
|
|
|
|
func (s *Session) Overlays() []source.Overlay {
|
2020-05-07 11:22:13 -06:00
|
|
|
|
s.overlayMu.Lock()
|
|
|
|
|
defer s.overlayMu.Unlock()
|
|
|
|
|
|
2020-06-02 08:57:20 -06:00
|
|
|
|
overlays := make([]source.Overlay, 0, len(s.overlays))
|
|
|
|
|
for _, overlay := range s.overlays {
|
|
|
|
|
overlays = append(overlays, overlay)
|
2020-05-07 11:22:13 -06:00
|
|
|
|
}
|
2020-06-02 08:57:20 -06:00
|
|
|
|
return overlays
|
2020-05-07 11:22:13 -06:00
|
|
|
|
}
|