internal/lsp: change file system to allow lazy reads

We split aquiring a "handle" from reading a files contents so that we can do the
former eagerly and the latter lazily.
We also "version" the handles so that the same file at different versions is a
different handle.

Change-Id: I06cc346d4b4c77d784aa454702c54689f2f177e0
Reviewed-on: https://go-review.googlesource.com/c/tools/+/179917
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Ian Cottrell 2019-05-31 19:41:39 -04:00
parent 8aaa1484dc
commit 2c3de6a5ae
6 changed files with 106 additions and 33 deletions

View File

@ -20,6 +20,7 @@ import (
func New() source.Cache {
index := atomic.AddInt64(&cacheIndex, 1)
c := &cache{
fs: &nativeFileSystem{},
id: strconv.FormatInt(index, 10),
fset: token.NewFileSet(),
}
@ -28,19 +29,22 @@ func New() source.Cache {
}
type cache struct {
nativeFileSystem
fs source.FileSystem
id string
fset *token.FileSet
}
func (c *cache) GetFile(uri span.URI) source.FileHandle {
return c.fs.GetFile(uri)
}
func (c *cache) NewSession(log xlog.Logger) source.Session {
index := atomic.AddInt64(&sessionIndex, 1)
s := &session{
cache: c,
id: strconv.FormatInt(index, 10),
log: log,
overlays: make(map[span.URI]*source.FileContent),
overlays: make(map[span.URI]*overlay),
filesWatchMap: NewWatchMap(),
}
debug.AddSession(debugSession{s})

View File

@ -5,6 +5,7 @@
package cache
import (
"context"
"io/ioutil"
"golang.org/x/tools/internal/lsp/source"
@ -14,9 +15,35 @@ import (
// nativeFileSystem implements FileSystem reading from the normal os file system.
type nativeFileSystem struct{}
func (nativeFileSystem) ReadFile(uri span.URI) *source.FileContent {
r := &source.FileContent{URI: uri}
filename, err := uri.Filename()
// nativeFileHandle implements FileHandle for nativeFileSystem
type nativeFileHandle struct {
fs *nativeFileSystem
identity source.FileIdentity
}
func (fs *nativeFileSystem) GetFile(uri span.URI) source.FileHandle {
return &nativeFileHandle{
fs: fs,
identity: source.FileIdentity{
URI: uri,
// TODO: decide what the version string is for a native file system
// could be the mtime?
Version: "",
},
}
}
func (h *nativeFileHandle) FileSystem() source.FileSystem {
return h.fs
}
func (h *nativeFileHandle) Identity() source.FileIdentity {
return h.identity
}
func (h *nativeFileHandle) Read(ctx context.Context) *source.FileContent {
r := &source.FileContent{}
filename, err := h.identity.URI.Filename()
if err != nil {
r.Error = err
return r

View File

@ -85,5 +85,5 @@ func (f *fileBase) read(ctx context.Context) {
}
}
// We don't know the content yet, so read it.
f.fc = f.view.Session().ReadFile(f.URI())
f.fc = f.view.Session().GetFile(f.URI()).Read(ctx)
}

View File

@ -31,12 +31,18 @@ type session struct {
viewMap map[span.URI]source.View
overlayMu sync.Mutex
overlays map[span.URI]*source.FileContent
overlays map[span.URI]*overlay
openFiles sync.Map
filesWatchMap *WatchMap
}
type overlay struct {
session *session
uri span.URI
content source.FileContent
}
func (s *session) Shutdown(ctx context.Context) {
s.viewMu.Lock()
defer s.viewMu.Unlock()
@ -186,12 +192,12 @@ func (s *session) IsOpen(uri span.URI) bool {
return open
}
func (s *session) ReadFile(uri span.URI) *source.FileContent {
func (s *session) GetFile(uri span.URI) source.FileHandle {
if overlay := s.readOverlay(uri); overlay != nil {
return overlay
}
// Fall back to the cache-level file system.
return s.Cache().ReadFile(uri)
return s.Cache().GetFile(uri)
}
func (s *session) SetOverlay(uri span.URI, data []byte) {
@ -205,14 +211,17 @@ func (s *session) SetOverlay(uri span.URI, data []byte) {
delete(s.overlays, uri)
return
}
s.overlays[uri] = &source.FileContent{
URI: uri,
s.overlays[uri] = &overlay{
session: s,
uri: uri,
content: source.FileContent{
Data: data,
Hash: hashContents(data),
},
}
}
func (s *session) readOverlay(uri span.URI) *source.FileContent {
func (s *session) readOverlay(uri span.URI) *overlay {
s.overlayMu.Lock()
defer s.overlayMu.Unlock()
@ -229,16 +238,31 @@ func (s *session) buildOverlay() map[string][]byte {
overlays := make(map[string][]byte)
for uri, overlay := range s.overlays {
if overlay.Error != nil {
if overlay.content.Error != nil {
continue
}
if filename, err := uri.Filename(); err == nil {
overlays[filename] = overlay.Data
overlays[filename] = overlay.content.Data
}
}
return overlays
}
func (o *overlay) FileSystem() source.FileSystem {
return o.session
}
func (o *overlay) Identity() source.FileIdentity {
return source.FileIdentity{
URI: o.uri,
Version: o.content.Hash,
}
}
func (o *overlay) Read(ctx context.Context) *source.FileContent {
return &o.content
}
type debugSession struct{ *session }
func (s debugSession) ID() string { return s.id }
@ -258,15 +282,15 @@ func (s debugSession) Files() []*debug.File {
s.overlayMu.Lock()
defer s.overlayMu.Unlock()
for _, overlay := range s.overlays {
f, ok := seen[overlay.URI]
f, ok := seen[overlay.uri]
if !ok {
f = &debug.File{Session: s, URI: overlay.URI}
seen[overlay.URI] = f
f = &debug.File{Session: s, URI: overlay.uri}
seen[overlay.uri] = f
files = append(files, f)
}
f.Data = string(overlay.Data)
f.Error = overlay.Error
f.Hash = overlay.Hash
f.Data = string(overlay.content.Data)
f.Error = overlay.content.Error
f.Hash = overlay.content.Hash
}
sort.Slice(files, func(i int, j int) bool {
return files[i].URI < files[j].URI
@ -278,13 +302,13 @@ func (s debugSession) File(hash string) *debug.File {
s.overlayMu.Lock()
defer s.overlayMu.Unlock()
for _, overlay := range s.overlays {
if overlay.Hash == hash {
if overlay.content.Hash == hash {
return &debug.File{
Session: s,
URI: overlay.URI,
Data: string(overlay.Data),
Error: overlay.Error,
Hash: overlay.Hash,
URI: overlay.uri,
Data: string(overlay.content.Data),
Error: overlay.content.Error,
Hash: overlay.content.Hash,
}
}
}

View File

@ -18,19 +18,37 @@ import (
"golang.org/x/tools/internal/span"
)
// FileContents is returned from FileSystem implementation to represent the
// FileContent is returned from FileSystem implementation to represent the
// contents of a file.
type FileContent struct {
URI span.URI
Data []byte
Error error
Hash string
}
// FileIdentity uniquely identifies a file at a version from a FileSystem.
type FileIdentity struct {
URI span.URI
Version string
}
// FileHandle represents a handle to a specific version of a single file from
// a specific file system.
type FileHandle interface {
// FileSystem returns the file system this handle was aquired from.
FileSystem() FileSystem
// Return the Identity for the file.
Identity() FileIdentity
// Read reads the contents of a file and returns it.
// If the file is not available, the returned FileContent will have no
// data and an error.
Read(ctx context.Context) *FileContent
}
// FileSystem is the interface to something that provides file contents.
type FileSystem interface {
// ReadFile reads the contents of a file and returns it.
ReadFile(uri span.URI) *FileContent
// GetFile returns a handle for the specified file.
GetFile(uri span.URI) FileHandle
}
// Cache abstracts the core logic of dealing with the environment from the

View File

@ -66,7 +66,7 @@ func (s *Server) applyChanges(ctx context.Context, params *protocol.DidChangeTex
}
uri := span.NewURI(params.TextDocument.URI)
fc := s.session.ReadFile(uri)
fc := s.session.GetFile(uri).Read(ctx)
if fc.Error != nil {
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found")
}