1
0
mirror of https://github.com/golang/go synced 2024-11-18 09:04:49 -07:00

internal/lsp: clean out the debug handling

This removes all the cache/session/view hooks from the lsp and instead
uses normal introspection from the client to find all the entities
rather than keeping shadow copies of the object graph in the debug page
handler.
This required the addition only of the ability to view the sessions open
on a cache and exposing the unique identifier for all the objects, both
of which are useful and reasonable things to have in the API anyway.

Change-Id: Ic19903e7b19832ca79290954ec373d4177789742
Reviewed-on: https://go-review.googlesource.com/c/tools/+/236197
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Ian Cottrell 2020-06-02 10:57:20 -04:00
parent 7147197327
commit ce53dc4445
10 changed files with 191 additions and 381 deletions

View File

@ -13,7 +13,6 @@ import (
"strconv"
"sync/atomic"
"golang.org/x/tools/internal/lsp/debug"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/memoize"
"golang.org/x/tools/internal/span"
@ -27,9 +26,6 @@ func New(ctx context.Context, options func(*source.Options)) *Cache {
fset: token.NewFileSet(),
options: options,
}
if di := debug.GetInstance(ctx); di != nil {
di.State.AddCache(debugCache{c})
}
return c
}
@ -84,9 +80,6 @@ func (c *Cache) NewSession(ctx context.Context) *Session {
options: source.DefaultOptions(),
overlays: make(map[span.URI]*overlay),
}
if di := debug.GetInstance(ctx); di != nil {
di.State.AddSession(DebugSession{s})
}
return s
}
@ -119,8 +112,5 @@ func hashContents(contents []byte) string {
var cacheIndex, sessionIndex, viewIndex int64
type debugCache struct{ *Cache }
func (c *Cache) ID() string { return c.id }
func (c debugCache) FileSet() *token.FileSet { return c.fset }
func (c debugCache) MemStats() map[reflect.Type]int { return c.store.Stats() }
func (c *Cache) ID() string { return c.id }
func (c *Cache) MemStats() map[reflect.Type]int { return c.store.Stats() }

View File

@ -1,64 +0,0 @@
// 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 (
"sort"
"golang.org/x/tools/internal/lsp/debug"
"golang.org/x/tools/internal/span"
)
type debugView struct{ *view }
func (v debugView) ID() string { return v.id }
func (v debugView) Session() debug.Session { return DebugSession{v.session} }
func (v debugView) Env() []string { return v.Options().Env }
type DebugSession struct{ *Session }
func (s DebugSession) ID() string { return s.id }
func (s DebugSession) Cache() debug.Cache { return debugCache{s.cache} }
func (s DebugSession) Files() []*debug.File {
var files []*debug.File
seen := make(map[span.URI]*debug.File)
s.overlayMu.Lock()
defer s.overlayMu.Unlock()
for _, overlay := range s.overlays {
f, ok := seen[overlay.uri]
if !ok {
f = &debug.File{Session: s, URI: overlay.uri}
seen[overlay.uri] = f
files = append(files, f)
}
f.Data = string(overlay.text)
f.Error = nil
f.Hash = overlay.hash
}
sort.Slice(files, func(i int, j int) bool {
return files[i].URI < files[j].URI
})
return files
}
func (s DebugSession) File(hash string) *debug.File {
s.overlayMu.Lock()
defer s.overlayMu.Unlock()
for _, overlay := range s.overlays {
if overlay.hash == hash {
return &debug.File{
Session: s,
URI: overlay.uri,
Data: string(overlay.text),
Error: nil,
Hash: overlay.hash,
}
}
}
return &debug.File{
Session: s,
Hash: hash,
}
}

View File

@ -13,7 +13,6 @@ import (
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/gocommand"
"golang.org/x/tools/internal/lsp/debug"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/xcontext"
@ -27,8 +26,8 @@ type Session struct {
options source.Options
viewMu sync.Mutex
views []*view
viewMap map[span.URI]*view
views []*View
viewMap map[span.URI]*View
overlayMu sync.Mutex
overlays map[span.URI]*overlay
@ -60,10 +59,17 @@ func (o *overlay) Identity() source.FileIdentity {
Kind: o.kind,
}
}
func (o *overlay) Read(ctx context.Context) ([]byte, string, error) {
return o.text, o.hash, nil
}
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 }
func (s *Session) ID() string { return s.id }
func (s *Session) Options() source.Options {
return s.options
}
@ -80,9 +86,6 @@ func (s *Session) Shutdown(ctx context.Context) {
}
s.views = nil
s.viewMap = nil
if di := debug.GetInstance(ctx); di != nil {
di.State.DropSession(DebugSession{s})
}
}
func (s *Session) Cache() source.Cache {
@ -98,18 +101,18 @@ func (s *Session) NewView(ctx context.Context, name string, folder span.URI, opt
}
s.views = append(s.views, v)
// we always need to drop the view map
s.viewMap = make(map[span.URI]*view)
s.viewMap = make(map[span.URI]*View)
return v, snapshot, nil
}
func (s *Session) createView(ctx context.Context, name string, folder span.URI, options source.Options, snapshotID uint64) (*view, *snapshot, error) {
func (s *Session) createView(ctx context.Context, name string, folder span.URI, options source.Options, snapshotID uint64) (*View, *snapshot, error) {
index := atomic.AddInt64(&viewIndex, 1)
// We want a true background context and not a detached context here
// the spans need to be unrelated and no tag values should pollute it.
baseCtx := event.Detach(xcontext.Detach(ctx))
backgroundCtx, cancel := context.WithCancel(baseCtx)
v := &view{
v := &View{
session: s,
initialized: make(chan struct{}),
id: strconv.FormatInt(index, 10),
@ -148,10 +151,6 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
// Initialize the view without blocking.
go v.initialize(xcontext.Detach(ctx), v.snapshot)
if di := debug.GetInstance(ctx); di != nil {
di.State.AddView(debugView{v})
}
return v, v.snapshot, nil
}
@ -173,7 +172,7 @@ func (s *Session) ViewOf(uri span.URI) (source.View, error) {
return s.viewOf(uri)
}
func (s *Session) viewOf(uri span.URI) (*view, error) {
func (s *Session) viewOf(uri span.URI) (*View, error) {
s.viewMu.Lock()
defer s.viewMu.Unlock()
@ -190,11 +189,11 @@ func (s *Session) viewOf(uri span.URI) (*view, error) {
return v, nil
}
func (s *Session) viewsOf(uri span.URI) []*view {
func (s *Session) viewsOf(uri span.URI) []*View {
s.viewMu.Lock()
defer s.viewMu.Unlock()
var views []*view
var views []*View
for _, view := range s.views {
if strings.HasPrefix(string(uri), string(view.Folder())) {
views = append(views, view)
@ -215,12 +214,12 @@ func (s *Session) Views() []source.View {
// bestView finds the best view to associate a given URI with.
// viewMu must be held when calling this method.
func (s *Session) bestView(uri span.URI) (*view, error) {
func (s *Session) bestView(uri span.URI) (*View, error) {
if len(s.views) == 0 {
return nil, errors.Errorf("no views in the session")
}
// we need to find the best view for this file
var longest *view
var longest *View
for _, view := range s.views {
if longest != nil && len(longest.Folder()) > len(view.Folder()) {
continue
@ -236,7 +235,7 @@ func (s *Session) bestView(uri span.URI) (*view, error) {
return s.views[0], nil
}
func (s *Session) removeView(ctx context.Context, view *view) error {
func (s *Session) removeView(ctx context.Context, view *View) error {
s.viewMu.Lock()
defer s.viewMu.Unlock()
i, err := s.dropView(ctx, view)
@ -251,7 +250,7 @@ func (s *Session) removeView(ctx context.Context, view *view) error {
return nil
}
func (s *Session) updateView(ctx context.Context, view *view, options source.Options) (*view, *snapshot, error) {
func (s *Session) updateView(ctx context.Context, view *View, options source.Options) (*View, *snapshot, error) {
s.viewMu.Lock()
defer s.viewMu.Unlock()
i, err := s.dropView(ctx, view)
@ -276,9 +275,9 @@ func (s *Session) updateView(ctx context.Context, view *view, options source.Opt
return v, snapshot, nil
}
func (s *Session) dropView(ctx context.Context, v *view) (int, error) {
func (s *Session) dropView(ctx context.Context, v *View) (int, error) {
// we always need to drop the view map
s.viewMap = make(map[span.URI]*view)
s.viewMap = make(map[span.URI]*View)
for i := range s.views {
if v == s.views[i] {
// we found the view, drop it and return the index it was found at
@ -291,7 +290,7 @@ func (s *Session) dropView(ctx context.Context, v *view) (int, error) {
}
func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModification) ([]source.Snapshot, error) {
views := make(map[*view]map[span.URI]source.FileHandle)
views := make(map[*View]map[span.URI]source.FileHandle)
overlays, err := s.updateOverlays(ctx, changes)
if err != nil {
@ -442,15 +441,13 @@ func (s *Session) readOverlay(uri span.URI) *overlay {
return nil
}
func (s *Session) UnsavedFiles() []span.URI {
func (s *Session) Overlays() []source.Overlay {
s.overlayMu.Lock()
defer s.overlayMu.Unlock()
var unsaved []span.URI
for uri, overlay := range s.overlays {
if !overlay.saved {
unsaved = append(unsaved, uri)
}
overlays := make([]source.Overlay, 0, len(s.overlays))
for _, overlay := range s.overlays {
overlays = append(overlays, overlay)
}
return unsaved
return overlays
}

View File

@ -29,7 +29,7 @@ import (
type snapshot struct {
id uint64
view *view
view *View
// mu guards all of the maps in the snapshot.
mu sync.Mutex
@ -667,7 +667,7 @@ func (s *snapshot) orphanedFileScopes() []interface{} {
return scopes
}
func contains(views []*view, view *view) bool {
func contains(views []*View, view *View) bool {
for _, v := range views {
if v == view {
return true

View File

@ -24,7 +24,6 @@ import (
"golang.org/x/tools/internal/event/keys"
"golang.org/x/tools/internal/gocommand"
"golang.org/x/tools/internal/imports"
"golang.org/x/tools/internal/lsp/debug"
"golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/memoize"
@ -33,7 +32,7 @@ import (
errors "golang.org/x/xerrors"
)
type view struct {
type View struct {
session *Session
id string
@ -137,7 +136,7 @@ type fileBase struct {
uris []span.URI
fname string
view *view
view *View
}
func (f *fileBase) URI() span.URI {
@ -153,29 +152,31 @@ func (f *fileBase) addURI(uri span.URI) int {
return len(f.uris)
}
func (v *view) ValidBuildConfiguration() bool {
func (v *View) ID() string { return v.id }
func (v *View) ValidBuildConfiguration() bool {
return v.hasValidBuildConfiguration
}
func (v *view) ModFiles() (span.URI, span.URI) {
func (v *View) ModFiles() (span.URI, span.URI) {
return v.realMod, v.tempMod
}
func (v *view) Session() source.Session {
func (v *View) Session() source.Session {
return v.session
}
// Name returns the user visible name of this view.
func (v *view) Name() string {
func (v *View) Name() string {
return v.name
}
// Folder returns the root of this view.
func (v *view) Folder() span.URI {
func (v *View) Folder() span.URI {
return v.folder
}
func (v *view) Options() source.Options {
func (v *View) Options() source.Options {
v.optionsMu.Lock()
defer v.optionsMu.Unlock()
return v.options
@ -193,7 +194,7 @@ func minorOptionsChange(a, b source.Options) bool {
return true
}
func (v *view) SetOptions(ctx context.Context, options source.Options) (source.View, error) {
func (v *View) SetOptions(ctx context.Context, options source.Options) (source.View, error) {
// no need to rebuild the view if the options were not materially changed
v.optionsMu.Lock()
if minorOptionsChange(v.options, options) {
@ -206,12 +207,12 @@ func (v *view) SetOptions(ctx context.Context, options source.Options) (source.V
return newView, err
}
func (v *view) Rebuild(ctx context.Context) (source.Snapshot, error) {
func (v *View) Rebuild(ctx context.Context) (source.Snapshot, error) {
_, snapshot, err := v.session.updateView(ctx, v, v.Options())
return snapshot, err
}
func (v *view) LookupBuiltin(ctx context.Context, name string) (*ast.Object, error) {
func (v *View) LookupBuiltin(ctx context.Context, name string) (*ast.Object, error) {
v.awaitInitialized(ctx)
if v.builtin == nil {
@ -241,7 +242,7 @@ func (v *view) LookupBuiltin(ctx context.Context, name string) (*ast.Object, err
return astObj, nil
}
func (v *view) buildBuiltinPackage(ctx context.Context, goFiles []string) error {
func (v *View) buildBuiltinPackage(ctx context.Context, goFiles []string) error {
if len(goFiles) != 1 {
return errors.Errorf("only expected 1 file, got %v", len(goFiles))
}
@ -271,7 +272,7 @@ func (v *view) buildBuiltinPackage(ctx context.Context, goFiles []string) error
return nil
}
func (v *view) WriteEnv(ctx context.Context, w io.Writer) error {
func (v *View) WriteEnv(ctx context.Context, w io.Writer) error {
v.optionsMu.Lock()
env, buildFlags := v.envLocked()
v.optionsMu.Unlock()
@ -291,7 +292,7 @@ func (v *view) WriteEnv(ctx context.Context, w io.Writer) error {
return nil
}
func (v *view) RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) error) error {
func (v *View) RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) error) error {
v.importsMu.Lock()
defer v.importsMu.Unlock()
@ -339,7 +340,7 @@ func (v *view) RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options)
return nil
}
func (v *view) refreshProcessEnv() {
func (v *View) refreshProcessEnv() {
start := time.Now()
v.importsMu.Lock()
@ -361,7 +362,7 @@ func (v *view) refreshProcessEnv() {
v.importsMu.Unlock()
}
func (v *view) buildProcessEnv(ctx context.Context) (*imports.ProcessEnv, error) {
func (v *View) buildProcessEnv(ctx context.Context) (*imports.ProcessEnv, error) {
v.optionsMu.Lock()
env, buildFlags := v.envLocked()
localPrefix, verboseOutput := v.options.LocalPrefix, v.options.VerboseOutput
@ -403,7 +404,7 @@ func (v *view) buildProcessEnv(ctx context.Context) (*imports.ProcessEnv, error)
return processEnv, nil
}
func (v *view) envLocked() ([]string, []string) {
func (v *View) envLocked() ([]string, []string) {
// We want to run the go commands with the -modfile flag if the version of go
// that we are using supports it.
buildFlags := v.options.BuildFlags
@ -415,11 +416,11 @@ func (v *view) envLocked() ([]string, []string) {
return env, buildFlags
}
func (v *view) contains(uri span.URI) bool {
func (v *View) contains(uri span.URI) bool {
return strings.HasPrefix(string(uri), string(v.folder))
}
func (v *view) mapFile(uri span.URI, f *fileBase) {
func (v *View) mapFile(uri span.URI, f *fileBase) {
v.filesByURI[uri] = f
if f.addURI(uri) == 1 {
basename := basename(f.filename())
@ -431,7 +432,7 @@ func basename(filename string) string {
return strings.ToLower(filepath.Base(filename))
}
func (v *view) relevantChange(c source.FileModification) bool {
func (v *View) relevantChange(c source.FileModification) bool {
// If the file is known to the view, the change is relevant.
known := v.knownFile(c.URI)
@ -444,7 +445,7 @@ func (v *view) relevantChange(c source.FileModification) bool {
return v.contains(c.URI) || known
}
func (v *view) knownFile(uri span.URI) bool {
func (v *View) knownFile(uri span.URI) bool {
v.mu.Lock()
defer v.mu.Unlock()
@ -454,7 +455,7 @@ func (v *view) knownFile(uri span.URI) bool {
// getFile returns a file for the given URI. It will always succeed because it
// adds the file to the managed set if needed.
func (v *view) getFile(uri span.URI) (*fileBase, error) {
func (v *View) getFile(uri span.URI) (*fileBase, error) {
v.mu.Lock()
defer v.mu.Unlock()
@ -476,7 +477,7 @@ func (v *view) getFile(uri span.URI) (*fileBase, error) {
//
// An error is only returned for an irreparable failure, for example, if the
// filename in question does not exist.
func (v *view) findFile(uri span.URI) (*fileBase, error) {
func (v *View) findFile(uri span.URI) (*fileBase, error) {
if f := v.filesByURI[uri]; f != nil {
// a perfect match
return f, nil
@ -507,11 +508,11 @@ func (v *view) findFile(uri span.URI) (*fileBase, error) {
return nil, nil
}
func (v *view) Shutdown(ctx context.Context) {
func (v *View) Shutdown(ctx context.Context) {
v.session.removeView(ctx, v)
}
func (v *view) shutdown(ctx context.Context) {
func (v *View) shutdown(ctx context.Context) {
// TODO: Cancel the view's initialization.
v.mu.Lock()
defer v.mu.Unlock()
@ -523,14 +524,11 @@ func (v *view) shutdown(ctx context.Context) {
os.Remove(v.tempMod.Filename())
os.Remove(tempSumFile(v.tempMod.Filename()))
}
if di := debug.GetInstance(ctx); di != nil {
di.State.DropView(debugView{v})
}
}
// Ignore checks if the given URI is a URI we ignore.
// As of right now, we only ignore files in the "builtin" package.
func (v *view) Ignore(uri span.URI) bool {
func (v *View) Ignore(uri span.URI) bool {
v.ignoredURIsMu.Lock()
defer v.ignoredURIsMu.Unlock()
@ -545,32 +543,32 @@ func (v *view) Ignore(uri span.URI) bool {
return ok
}
func (v *view) addIgnoredFile(uri span.URI) {
func (v *View) addIgnoredFile(uri span.URI) {
v.ignoredURIsMu.Lock()
defer v.ignoredURIsMu.Unlock()
v.ignoredURIs[uri] = struct{}{}
}
func (v *view) BackgroundContext() context.Context {
func (v *View) BackgroundContext() context.Context {
v.mu.Lock()
defer v.mu.Unlock()
return v.backgroundCtx
}
func (v *view) Snapshot() source.Snapshot {
func (v *View) Snapshot() source.Snapshot {
return v.getSnapshot()
}
func (v *view) getSnapshot() *snapshot {
func (v *View) getSnapshot() *snapshot {
v.snapshotMu.Lock()
defer v.snapshotMu.Unlock()
return v.snapshot
}
func (v *view) initialize(ctx context.Context, s *snapshot) {
func (v *View) initialize(ctx context.Context, s *snapshot) {
v.initializeOnce.Do(func() {
defer close(v.initialized)
@ -581,7 +579,7 @@ func (v *view) initialize(ctx context.Context, s *snapshot) {
})
}
func (v *view) awaitInitialized(ctx context.Context) {
func (v *View) awaitInitialized(ctx context.Context) {
select {
case <-ctx.Done():
case <-v.initialized:
@ -591,7 +589,7 @@ func (v *view) awaitInitialized(ctx context.Context) {
// invalidateContent invalidates the content of a Go file,
// including any position and type information that depends on it.
// It returns true if we were already tracking the given file, false otherwise.
func (v *view) invalidateContent(ctx context.Context, uris map[span.URI]source.FileHandle, forceReloadMetadata bool) source.Snapshot {
func (v *View) invalidateContent(ctx context.Context, uris map[span.URI]source.FileHandle, forceReloadMetadata bool) source.Snapshot {
// Detach the context so that content invalidation cannot be canceled.
ctx = xcontext.Detach(ctx)
@ -610,7 +608,7 @@ func (v *view) invalidateContent(ctx context.Context, uris map[span.URI]source.F
return v.snapshot
}
func (v *view) cancelBackground() {
func (v *View) cancelBackground() {
v.mu.Lock()
defer v.mu.Unlock()
if v.cancel == nil {
@ -621,7 +619,7 @@ func (v *view) cancelBackground() {
v.backgroundCtx, v.cancel = context.WithCancel(v.baseCtx)
}
func (v *view) setBuildInformation(ctx context.Context, folder span.URI, env []string, modfileFlagEnabled bool) error {
func (v *View) setBuildInformation(ctx context.Context, folder span.URI, env []string, modfileFlagEnabled bool) error {
if err := checkPathCase(folder.Filename()); err != nil {
return fmt.Errorf("invalid workspace configuration: %w", err)
}
@ -721,7 +719,7 @@ func isSubdirectory(root, leaf string) bool {
// getGoEnv sets the view's build information's GOPATH, GOCACHE, GOPRIVATE, and
// GOPACKAGESDRIVER values. It also returns the view's GOMOD value, which need
// not be cached.
func (v *view) getGoEnv(ctx context.Context, env []string) (string, error) {
func (v *View) getGoEnv(ctx context.Context, env []string) (string, error) {
var gocache, gopath, gopackagesdriver, goprivate bool
isGoCommand := func(gopackagesdriver string) bool {
return gopackagesdriver == "" || gopackagesdriver == "off"
@ -792,7 +790,7 @@ func (v *view) getGoEnv(ctx context.Context, env []string) (string, error) {
}
func (v *view) IsGoPrivatePath(target string) bool {
func (v *View) IsGoPrivatePath(target string) bool {
return globsMatchPath(v.goprivate, target)
}
@ -840,7 +838,7 @@ func globsMatchPath(globs, target string) bool {
// This function will return the main go.mod file for this folder if it exists and whether the -modfile
// flag exists for this version of go.
func (v *view) modfileFlagExists(ctx context.Context, env []string) (bool, error) {
func (v *View) modfileFlagExists(ctx context.Context, env []string) (bool, error) {
// Check the go version by running "go list" with modules off.
// Borrowed from internal/imports/mod.go:620.
const format = `{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}}`

View File

@ -23,7 +23,14 @@ import (
func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) {
switch params.Command {
case source.CommandTest:
if len(s.session.UnsavedFiles()) != 0 {
unsaved := false
for _, overlay := range s.session.Overlays() {
if !overlay.Saved() {
unsaved = true
break
}
}
if unsaved {
return nil, s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
Type: protocol.Error,
Message: "could not run tests, there are unsaved files in the view",

View File

@ -8,7 +8,6 @@ import (
"bytes"
"context"
"fmt"
"go/token"
"html/template"
"io"
"log"
@ -18,7 +17,6 @@ import (
"os"
"path"
"path/filepath"
"reflect"
"runtime"
rpprof "runtime/pprof"
"strconv"
@ -34,9 +32,9 @@ import (
"golang.org/x/tools/internal/event/export/prometheus"
"golang.org/x/tools/internal/event/keys"
"golang.org/x/tools/internal/event/label"
"golang.org/x/tools/internal/lsp/cache"
"golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/span"
"golang.org/x/xerrors"
)
@ -67,12 +65,9 @@ type Instance struct {
// State holds debugging information related to the server state.
type State struct {
mu sync.Mutex
caches objset
sessions objset
views objset
clients objset
servers objset
mu sync.Mutex
clients objset
servers objset
}
type ider interface {
@ -107,38 +102,75 @@ func (s *objset) find(id string) ider {
}
// Caches returns the set of Cache objects currently being served.
func (st *State) Caches() []Cache {
st.mu.Lock()
defer st.mu.Unlock()
caches := make([]Cache, len(st.caches.objs))
for i, c := range st.caches.objs {
caches[i] = c.(Cache)
func (st *State) Caches() []*cache.Cache {
var caches []*cache.Cache
seen := make(map[string]struct{})
for _, client := range st.Clients() {
cache, ok := client.Session().Cache().(*cache.Cache)
if !ok {
continue
}
if _, found := seen[cache.ID()]; found {
continue
}
seen[cache.ID()] = struct{}{}
caches = append(caches, cache)
}
return caches
}
// Cache returns the Cache that matches the supplied id.
func (st *State) Cache(id string) *cache.Cache {
for _, c := range st.Caches() {
if c.ID() == id {
return c
}
}
return nil
}
// Sessions returns the set of Session objects currently being served.
func (st *State) Sessions() []Session {
st.mu.Lock()
defer st.mu.Unlock()
sessions := make([]Session, len(st.sessions.objs))
for i, s := range st.sessions.objs {
sessions[i] = s.(Session)
func (st *State) Sessions() []*cache.Session {
var sessions []*cache.Session
for _, client := range st.Clients() {
sessions = append(sessions, client.Session())
}
return sessions
}
// Session returns the Session that matches the supplied id.
func (st *State) Session(id string) *cache.Session {
for _, s := range st.Sessions() {
if s.ID() == id {
return s
}
}
return nil
}
// Views returns the set of View objects currently being served.
func (st *State) Views() []View {
st.mu.Lock()
defer st.mu.Unlock()
views := make([]View, len(st.views.objs))
for i, v := range st.views.objs {
views[i] = v.(View)
func (st *State) Views() []*cache.View {
var views []*cache.View
for _, s := range st.Sessions() {
for _, v := range s.Views() {
if cv, ok := v.(*cache.View); ok {
views = append(views, cv)
}
}
}
return views
}
// View returns the View that matches the supplied id.
func (st *State) View(id string) *cache.View {
for _, v := range st.Views() {
if v.ID() == id {
return v
}
}
return nil
}
// Clients returns the set of Clients currently being served.
func (st *State) Clients() []Client {
st.mu.Lock()
@ -164,7 +196,7 @@ func (st *State) Servers() []Server {
// A Client is an incoming connection from a remote client.
type Client interface {
ID() string
Session() Session
Session() *cache.Session
DebugAddress() string
Logfile() string
ServerID() string
@ -178,80 +210,6 @@ type Server interface {
ClientID() string
}
// A Cache is an in-memory cache.
type Cache interface {
ID() string
FileSet() *token.FileSet
MemStats() map[reflect.Type]int
}
// A Session is an LSP serving session.
type Session interface {
ID() string
Cache() Cache
Files() []*File
File(hash string) *File
}
// A View is a root directory within a Session.
type View interface {
ID() string
Name() string
Folder() span.URI
Session() Session
}
// A File is a file within a session.
type File struct {
Session Session
URI span.URI
Data string
Error error
Hash string
}
// AddCache adds a cache to the set being served.
func (st *State) AddCache(cache Cache) {
st.mu.Lock()
defer st.mu.Unlock()
st.caches.add(cache)
}
// DropCache drops a cache from the set being served.
func (st *State) DropCache(cache Cache) {
st.mu.Lock()
defer st.mu.Unlock()
st.caches.drop(cache)
}
// AddSession adds a session to the set being served.
func (st *State) AddSession(session Session) {
st.mu.Lock()
defer st.mu.Unlock()
st.sessions.add(session)
}
// DropSession drops a session from the set being served.
func (st *State) DropSession(session Session) {
st.mu.Lock()
defer st.mu.Unlock()
st.sessions.drop(session)
}
// AddView adds a view to the set being served.
func (st *State) AddView(view View) {
st.mu.Lock()
defer st.mu.Unlock()
st.views.add(view)
}
// DropView drops a view from the set being served.
func (st *State) DropView(view View) {
st.mu.Lock()
defer st.mu.Unlock()
st.views.drop(view)
}
// AddClient adds a client to the set being served.
func (st *State) AddClient(client Client) {
st.mu.Lock()
@ -282,52 +240,11 @@ func (st *State) DropServer(server Server) {
}
func (i *Instance) getCache(r *http.Request) interface{} {
i.State.mu.Lock()
defer i.State.mu.Unlock()
id := path.Base(r.URL.Path)
c, ok := i.State.caches.find(id).(Cache)
if !ok {
return nil
}
result := struct {
Cache
Sessions []Session
}{
Cache: c,
}
// now find all the views that belong to this session
for _, vd := range i.State.sessions.objs {
v := vd.(Session)
if v.Cache().ID() == id {
result.Sessions = append(result.Sessions, v)
}
}
return result
return i.State.Cache(path.Base(r.URL.Path))
}
func (i *Instance) getSession(r *http.Request) interface{} {
i.State.mu.Lock()
defer i.State.mu.Unlock()
id := path.Base(r.URL.Path)
s, ok := i.State.sessions.find(id).(Session)
if !ok {
return nil
}
result := struct {
Session
Views []View
}{
Session: s,
}
// now find all the views that belong to this session
for _, vd := range i.State.views.objs {
v := vd.(View)
if v.Session().ID() == id {
result.Views = append(result.Views, v)
}
}
return result
return i.State.Session(path.Base(r.URL.Path))
}
func (i Instance) getClient(r *http.Request) interface{} {
@ -353,26 +270,22 @@ func (i Instance) getServer(r *http.Request) interface{} {
}
func (i Instance) getView(r *http.Request) interface{} {
i.State.mu.Lock()
defer i.State.mu.Unlock()
id := path.Base(r.URL.Path)
v, ok := i.State.views.find(id).(View)
if !ok {
return nil
}
return v
return i.State.View(path.Base(r.URL.Path))
}
func (i *Instance) getFile(r *http.Request) interface{} {
i.State.mu.Lock()
defer i.State.mu.Unlock()
hash := path.Base(r.URL.Path)
identifier := path.Base(r.URL.Path)
sid := path.Base(path.Dir(r.URL.Path))
s, ok := i.State.sessions.find(sid).(Session)
if !ok {
s := i.State.Session(sid)
if s == nil {
return nil
}
return s.File(hash)
for _, o := range s.Overlays() {
if o.Identity().Identifier == identifier {
return o
}
}
return nil
}
func (i *Instance) getInfo(r *http.Request) interface{} {
@ -625,6 +538,10 @@ func fuint32(v uint32) string {
return commas(strconv.FormatUint(uint64(v), 10))
}
func fcontent(v []byte) string {
return string(v)
}
var baseTemplate = template.Must(template.New("").Parse(`
<html>
<head>
@ -664,10 +581,11 @@ Unknown page
{{define "serverlink"}}<a href="/server/{{.}}">Server {{.}}</a>{{end}}
{{define "sessionlink"}}<a href="/session/{{.}}">Session {{.}}</a>{{end}}
{{define "viewlink"}}<a href="/view/{{.}}">View {{.}}</a>{{end}}
{{define "filelink"}}<a href="/file/{{.Session.ID}}/{{.Hash}}">{{.URI}}</a>{{end}}
{{define "filelink"}}<a href="/file/{{.SessionID}}/{{.Identifier}}">{{.URI}}</a>{{end}}
`)).Funcs(template.FuncMap{
"fuint64": fuint64,
"fuint32": fuint32,
"fuint64": fuint64,
"fuint32": fuint32,
"fcontent": fcontent,
"localAddress": func(s string) string {
// Try to translate loopback addresses to localhost, both for cosmetics and
// because unspecified ipv6 addresses can break links on Windows.
@ -785,8 +703,8 @@ var sessionTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
From: <b>{{template "cachelink" .Cache.ID}}</b><br>
<h2>Views</h2>
<ul>{{range .Views}}<li>{{.Name}} is {{template "viewlink" .ID}} in {{.Folder}}</li>{{end}}</ul>
<h2>Files</h2>
<ul>{{range .Files}}<li>{{template "filelink" .}}</li>{{end}}</ul>
<h2>Overlays</h2>
<ul>{{range .Overlays}}<li>{{template "filelink" .Identity}}</li>{{end}}</ul>
{{end}}
`))
@ -797,18 +715,21 @@ Name: <b>{{.Name}}</b><br>
Folder: <b>{{.Folder}}</b><br>
From: <b>{{template "sessionlink" .Session.ID}}</b><br>
<h2>Environment</h2>
<ul>{{range .Env}}<li>{{.}}</li>{{end}}</ul>
<ul>{{range .Options.Env}}<li>{{.}}</li>{{end}}</ul>
{{end}}
`))
var fileTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
{{define "title"}}File {{.Hash}}{{end}}
{{define "title"}}Overlay {{.Identity.Identifier}}{{end}}
{{define "body"}}
From: <b>{{template "sessionlink" .Session.ID}}</b><br>
URI: <b>{{.URI}}</b><br>
Hash: <b>{{.Hash}}</b><br>
Error: <b>{{.Error}}</b><br>
{{with .Identity}}
From: <b>{{template "sessionlink" .SessionID}}</b><br>
URI: <b>{{.URI}}</b><br>
Identifier: <b>{{.Identifier}}</b><br>
Version: <b>{{.Version}}</b><br>
Kind: <b>{{.Kind}}</b><br>
{{end}}
<h3>Contents</h3>
<pre>{{.Data}}</pre>
<pre>{{fcontent .Data}}</pre>
{{end}}
`))

View File

@ -1,54 +0,0 @@
// Copyright 2020 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 debug
import "testing"
type fakeCache struct {
Cache
id string
}
func (c fakeCache) ID() string {
return c.id
}
func TestState(t *testing.T) {
c1 := fakeCache{id: "1"}
c2 := fakeCache{id: "2"}
c3 := fakeCache{id: "3"}
var s State
s.AddCache(c1)
s.AddCache(c2)
s.AddCache(c3)
compareCaches := func(desc string, want []fakeCache) {
t.Run(desc, func(t *testing.T) {
caches := s.Caches()
if gotLen, wantLen := len(caches), len(want); gotLen != wantLen {
t.Fatalf("len(Caches) = %d, want %d", gotLen, wantLen)
}
for i, got := range caches {
if got != want[i] {
t.Errorf("Caches[%d] = %v, want %v", i, got, want[i])
}
}
})
}
compareCaches("initial load", []fakeCache{c1, c2, c3})
s.DropCache(c2)
compareCaches("dropped cache 2", []fakeCache{c1, c3})
s.DropCache(c2)
compareCaches("duplicate drop", []fakeCache{c1, c3})
s.AddCache(c2)
compareCaches("re-add cache 2", []fakeCache{c1, c3, c2})
s.DropCache(c1)
s.DropCache(c2)
s.DropCache(c3)
compareCaches("drop all", []fakeCache{})
}

View File

@ -95,8 +95,8 @@ type debugClient struct {
serverID string
}
func (c debugClient) Session() debug.Session {
return cache.DebugSession{Session: c.session}
func (c debugClient) Session() *cache.Session {
return c.session
}
func (c debugClient) ServerID() string {
@ -462,7 +462,7 @@ func handshaker(client *debugClient, goplsPath string, handler jsonrpc2.Handler)
client.goplsPath = req.GoplsPath
resp := handshakeResponse{
ClientID: client.id,
SessionID: cache.DebugSession{Session: client.session}.ID(),
SessionID: client.session.ID(),
GoplsPath: goplsPath,
}
if di := debug.GetInstance(ctx); di != nil {

View File

@ -189,8 +189,8 @@ type Session interface {
// It returns the resulting snapshots, a guaranteed one per view.
DidModifyFiles(ctx context.Context, changes []FileModification) ([]Snapshot, error)
// UnsavedFiles returns a slice of open but unsaved files in the session.
UnsavedFiles() []span.URI
// Overlays returns a slice of file overlays for the session.
Overlays() []Overlay
// Options returns a copy of the SessionOptions for this session.
Options() Options
@ -199,6 +199,21 @@ type Session interface {
SetOptions(Options)
}
// Overlay is the type for a file held in memory on a session.
type Overlay interface {
// Session returns the session this overlay belongs to.
Session() Session
// Identity returns the FileIdentity for the overlay.
Identity() FileIdentity
// Saved returns whether this overlay has been saved to disk.
Saved() bool
// Data is the contents of the overlay held in memory.
Data() []byte
}
// FileModification represents a modification to a file.
type FileModification struct {
URI span.URI