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:
parent
8aaa1484dc
commit
2c3de6a5ae
|
@ -20,6 +20,7 @@ import (
|
||||||
func New() source.Cache {
|
func New() source.Cache {
|
||||||
index := atomic.AddInt64(&cacheIndex, 1)
|
index := atomic.AddInt64(&cacheIndex, 1)
|
||||||
c := &cache{
|
c := &cache{
|
||||||
|
fs: &nativeFileSystem{},
|
||||||
id: strconv.FormatInt(index, 10),
|
id: strconv.FormatInt(index, 10),
|
||||||
fset: token.NewFileSet(),
|
fset: token.NewFileSet(),
|
||||||
}
|
}
|
||||||
|
@ -28,19 +29,22 @@ func New() source.Cache {
|
||||||
}
|
}
|
||||||
|
|
||||||
type cache struct {
|
type cache struct {
|
||||||
nativeFileSystem
|
fs source.FileSystem
|
||||||
|
|
||||||
id string
|
id string
|
||||||
fset *token.FileSet
|
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 {
|
func (c *cache) NewSession(log xlog.Logger) source.Session {
|
||||||
index := atomic.AddInt64(&sessionIndex, 1)
|
index := atomic.AddInt64(&sessionIndex, 1)
|
||||||
s := &session{
|
s := &session{
|
||||||
cache: c,
|
cache: c,
|
||||||
id: strconv.FormatInt(index, 10),
|
id: strconv.FormatInt(index, 10),
|
||||||
log: log,
|
log: log,
|
||||||
overlays: make(map[span.URI]*source.FileContent),
|
overlays: make(map[span.URI]*overlay),
|
||||||
filesWatchMap: NewWatchMap(),
|
filesWatchMap: NewWatchMap(),
|
||||||
}
|
}
|
||||||
debug.AddSession(debugSession{s})
|
debug.AddSession(debugSession{s})
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"golang.org/x/tools/internal/lsp/source"
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
|
@ -14,9 +15,35 @@ import (
|
||||||
// nativeFileSystem implements FileSystem reading from the normal os file system.
|
// nativeFileSystem implements FileSystem reading from the normal os file system.
|
||||||
type nativeFileSystem struct{}
|
type nativeFileSystem struct{}
|
||||||
|
|
||||||
func (nativeFileSystem) ReadFile(uri span.URI) *source.FileContent {
|
// nativeFileHandle implements FileHandle for nativeFileSystem
|
||||||
r := &source.FileContent{URI: uri}
|
type nativeFileHandle struct {
|
||||||
filename, err := uri.Filename()
|
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 {
|
if err != nil {
|
||||||
r.Error = err
|
r.Error = err
|
||||||
return r
|
return r
|
||||||
|
|
|
@ -85,5 +85,5 @@ func (f *fileBase) read(ctx context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We don't know the content yet, so read it.
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,12 +31,18 @@ type session struct {
|
||||||
viewMap map[span.URI]source.View
|
viewMap map[span.URI]source.View
|
||||||
|
|
||||||
overlayMu sync.Mutex
|
overlayMu sync.Mutex
|
||||||
overlays map[span.URI]*source.FileContent
|
overlays map[span.URI]*overlay
|
||||||
|
|
||||||
openFiles sync.Map
|
openFiles sync.Map
|
||||||
filesWatchMap *WatchMap
|
filesWatchMap *WatchMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type overlay struct {
|
||||||
|
session *session
|
||||||
|
uri span.URI
|
||||||
|
content source.FileContent
|
||||||
|
}
|
||||||
|
|
||||||
func (s *session) Shutdown(ctx context.Context) {
|
func (s *session) Shutdown(ctx context.Context) {
|
||||||
s.viewMu.Lock()
|
s.viewMu.Lock()
|
||||||
defer s.viewMu.Unlock()
|
defer s.viewMu.Unlock()
|
||||||
|
@ -186,12 +192,12 @@ func (s *session) IsOpen(uri span.URI) bool {
|
||||||
return open
|
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 {
|
if overlay := s.readOverlay(uri); overlay != nil {
|
||||||
return overlay
|
return overlay
|
||||||
}
|
}
|
||||||
// Fall back to the cache-level file system.
|
// 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) {
|
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)
|
delete(s.overlays, uri)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.overlays[uri] = &source.FileContent{
|
s.overlays[uri] = &overlay{
|
||||||
URI: uri,
|
session: s,
|
||||||
Data: data,
|
uri: uri,
|
||||||
Hash: hashContents(data),
|
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()
|
s.overlayMu.Lock()
|
||||||
defer s.overlayMu.Unlock()
|
defer s.overlayMu.Unlock()
|
||||||
|
|
||||||
|
@ -229,16 +238,31 @@ func (s *session) buildOverlay() map[string][]byte {
|
||||||
|
|
||||||
overlays := make(map[string][]byte)
|
overlays := make(map[string][]byte)
|
||||||
for uri, overlay := range s.overlays {
|
for uri, overlay := range s.overlays {
|
||||||
if overlay.Error != nil {
|
if overlay.content.Error != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if filename, err := uri.Filename(); err == nil {
|
if filename, err := uri.Filename(); err == nil {
|
||||||
overlays[filename] = overlay.Data
|
overlays[filename] = overlay.content.Data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return overlays
|
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 }
|
type debugSession struct{ *session }
|
||||||
|
|
||||||
func (s debugSession) ID() string { return s.id }
|
func (s debugSession) ID() string { return s.id }
|
||||||
|
@ -258,15 +282,15 @@ func (s debugSession) Files() []*debug.File {
|
||||||
s.overlayMu.Lock()
|
s.overlayMu.Lock()
|
||||||
defer s.overlayMu.Unlock()
|
defer s.overlayMu.Unlock()
|
||||||
for _, overlay := range s.overlays {
|
for _, overlay := range s.overlays {
|
||||||
f, ok := seen[overlay.URI]
|
f, ok := seen[overlay.uri]
|
||||||
if !ok {
|
if !ok {
|
||||||
f = &debug.File{Session: s, URI: overlay.URI}
|
f = &debug.File{Session: s, URI: overlay.uri}
|
||||||
seen[overlay.URI] = f
|
seen[overlay.uri] = f
|
||||||
files = append(files, f)
|
files = append(files, f)
|
||||||
}
|
}
|
||||||
f.Data = string(overlay.Data)
|
f.Data = string(overlay.content.Data)
|
||||||
f.Error = overlay.Error
|
f.Error = overlay.content.Error
|
||||||
f.Hash = overlay.Hash
|
f.Hash = overlay.content.Hash
|
||||||
}
|
}
|
||||||
sort.Slice(files, func(i int, j int) bool {
|
sort.Slice(files, func(i int, j int) bool {
|
||||||
return files[i].URI < files[j].URI
|
return files[i].URI < files[j].URI
|
||||||
|
@ -278,13 +302,13 @@ func (s debugSession) File(hash string) *debug.File {
|
||||||
s.overlayMu.Lock()
|
s.overlayMu.Lock()
|
||||||
defer s.overlayMu.Unlock()
|
defer s.overlayMu.Unlock()
|
||||||
for _, overlay := range s.overlays {
|
for _, overlay := range s.overlays {
|
||||||
if overlay.Hash == hash {
|
if overlay.content.Hash == hash {
|
||||||
return &debug.File{
|
return &debug.File{
|
||||||
Session: s,
|
Session: s,
|
||||||
URI: overlay.URI,
|
URI: overlay.uri,
|
||||||
Data: string(overlay.Data),
|
Data: string(overlay.content.Data),
|
||||||
Error: overlay.Error,
|
Error: overlay.content.Error,
|
||||||
Hash: overlay.Hash,
|
Hash: overlay.content.Hash,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,19 +18,37 @@ import (
|
||||||
"golang.org/x/tools/internal/span"
|
"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.
|
// contents of a file.
|
||||||
type FileContent struct {
|
type FileContent struct {
|
||||||
URI span.URI
|
|
||||||
Data []byte
|
Data []byte
|
||||||
Error error
|
Error error
|
||||||
Hash string
|
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.
|
// FileSystem is the interface to something that provides file contents.
|
||||||
type FileSystem interface {
|
type FileSystem interface {
|
||||||
// ReadFile reads the contents of a file and returns it.
|
// GetFile returns a handle for the specified file.
|
||||||
ReadFile(uri span.URI) *FileContent
|
GetFile(uri span.URI) FileHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache abstracts the core logic of dealing with the environment from the
|
// Cache abstracts the core logic of dealing with the environment from the
|
||||||
|
|
|
@ -66,7 +66,7 @@ func (s *Server) applyChanges(ctx context.Context, params *protocol.DidChangeTex
|
||||||
}
|
}
|
||||||
|
|
||||||
uri := span.NewURI(params.TextDocument.URI)
|
uri := span.NewURI(params.TextDocument.URI)
|
||||||
fc := s.session.ReadFile(uri)
|
fc := s.session.GetFile(uri).Read(ctx)
|
||||||
if fc.Error != nil {
|
if fc.Error != nil {
|
||||||
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found")
|
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue