diff --git a/internal/lsp/cache/cache.go b/internal/lsp/cache/cache.go
index f235d6ebd3..0f26c234aa 100644
--- a/internal/lsp/cache/cache.go
+++ b/internal/lsp/cache/cache.go
@@ -13,6 +13,7 @@ import (
"strconv"
"sync/atomic"
+ "golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/memoize"
"golang.org/x/tools/internal/span"
@@ -80,6 +81,7 @@ func (c *Cache) NewSession(ctx context.Context) *Session {
options: source.DefaultOptions(),
overlays: make(map[span.URI]*overlay),
}
+ event.Log(ctx, "New session", KeyCreateSession.Of(s))
return s
}
diff --git a/internal/lsp/cache/keys.go b/internal/lsp/cache/keys.go
new file mode 100644
index 0000000000..449daba3a9
--- /dev/null
+++ b/internal/lsp/cache/keys.go
@@ -0,0 +1,52 @@
+// 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 cache
+
+import (
+ "io"
+
+ "golang.org/x/tools/internal/event/label"
+)
+
+var (
+ KeyCreateSession = NewSessionKey("create_session", "A new session was added")
+ KeyUpdateSession = NewSessionKey("update_session", "Updated information about a session")
+ KeyShutdownSession = NewSessionKey("shutdown_session", "A session was shut down")
+)
+
+// SessionKey represents an event label key that has a *Session value.
+type SessionKey struct {
+ name string
+ description string
+}
+
+// NewSessionKey creates a new Key for *Session values.
+func NewSessionKey(name, description string) *SessionKey {
+ return &SessionKey{name: name, description: description}
+}
+
+func (k *SessionKey) Name() string { return k.name }
+func (k *SessionKey) Description() string { return k.description }
+
+func (k *SessionKey) Format(w io.Writer, buf []byte, l label.Label) {
+ io.WriteString(w, k.From(l).ID())
+}
+
+// Of creates a new Label with this key and the supplied session.
+func (k *SessionKey) Of(v *Session) label.Label { return label.OfValue(k, v) }
+
+// Get can be used to get the session for the key from a label.Map.
+func (k *SessionKey) Get(lm label.Map) *Session {
+ if t := lm.Find(k); t.Valid() {
+ return k.From(t)
+ }
+ return nil
+}
+
+// From can be used to get the session value from a Label.
+func (k *SessionKey) From(t label.Label) *Session {
+ err, _ := t.UnpackValue().(*Session)
+ return err
+}
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index a902386a61..2787cd657c 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -68,7 +68,8 @@ 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) ID() string { return s.id }
+func (s *Session) String() string { return s.id }
func (s *Session) Options() source.Options {
return s.options
@@ -86,6 +87,7 @@ func (s *Session) Shutdown(ctx context.Context) {
}
s.views = nil
s.viewMap = nil
+ event.Log(ctx, "Shutdown session", KeyShutdownSession.Of(s))
}
func (s *Session) Cache() source.Cache {
diff --git a/internal/lsp/debug/serve.go b/internal/lsp/debug/serve.go
index 9642533118..0a918e1b92 100644
--- a/internal/lsp/debug/serve.go
+++ b/internal/lsp/debug/serve.go
@@ -35,6 +35,7 @@ import (
"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/lsp/source"
"golang.org/x/xerrors"
)
@@ -149,6 +150,16 @@ func (st *State) Clients() []*Client {
return clients
}
+// View returns the View that matches the supplied id.
+func (st *State) Client(id string) *Client {
+ for _, c := range st.Clients() {
+ if c.Session.ID() == id {
+ return c
+ }
+ }
+ return nil
+}
+
// Servers returns the set of Servers the instance is currently connected to.
func (st *State) Servers() []*Server {
st.mu.Lock()
@@ -160,7 +171,6 @@ func (st *State) Servers() []*Server {
// A Client is an incoming connection from a remote client.
type Client struct {
- ID string
Session *cache.Session
DebugAddress string
Logfile string
@@ -178,18 +188,18 @@ type Server struct {
}
// AddClient adds a client to the set being served.
-func (st *State) AddClient(client *Client) {
+func (st *State) addClient(session *cache.Session) {
st.mu.Lock()
defer st.mu.Unlock()
- st.clients = append(st.clients, client)
+ st.clients = append(st.clients, &Client{Session: session})
}
// DropClient removes a client from the set being served.
-func (st *State) DropClient(id string) {
+func (st *State) dropClient(session source.Session) {
st.mu.Lock()
defer st.mu.Unlock()
for i, c := range st.clients {
- if c.ID == id {
+ if c.Session == session {
copy(st.clients[i:], st.clients[i+1:])
st.clients[len(st.clients)-1] = nil
st.clients = st.clients[:len(st.clients)-1]
@@ -200,14 +210,14 @@ func (st *State) DropClient(id string) {
// AddServer adds a server to the set being queried. In practice, there should
// be at most one remote server.
-func (st *State) AddServer(server *Server) {
+func (st *State) addServer(server *Server) {
st.mu.Lock()
defer st.mu.Unlock()
st.servers = append(st.servers, server)
}
// DropServer drops a server from the set being queried.
-func (st *State) DropServer(id string) {
+func (st *State) dropServer(id string) {
st.mu.Lock()
defer st.mu.Unlock()
for i, s := range st.servers {
@@ -229,15 +239,7 @@ func (i *Instance) getSession(r *http.Request) interface{} {
}
func (i Instance) getClient(r *http.Request) interface{} {
- i.State.mu.Lock()
- defer i.State.mu.Unlock()
- id := path.Base(r.URL.Path)
- for _, c := range i.State.clients {
- if c.ID == id {
- return c
- }
- }
- return nil
+ return i.State.Client(path.Base(r.URL.Path))
}
func (i Instance) getServer(r *http.Request) interface{} {
@@ -480,6 +482,34 @@ func makeInstanceExporter(i *Instance) event.Exporter {
if i.traces != nil {
ctx = i.traces.ProcessEvent(ctx, ev, lm)
}
+ if event.IsLog(ev) {
+ if s := cache.KeyCreateSession.Get(ev); s != nil {
+ i.State.addClient(s)
+ }
+ if sid := tag.NewServer.Get(ev); sid != "" {
+ i.State.addServer(&Server{
+ ID: sid,
+ Logfile: tag.Logfile.Get(ev),
+ DebugAddress: tag.DebugAddress.Get(ev),
+ GoplsPath: tag.GoplsPath.Get(ev),
+ ClientID: tag.ClientID.Get(ev),
+ })
+ }
+ if s := cache.KeyShutdownSession.Get(ev); s != nil {
+ i.State.dropClient(s)
+ }
+ if sid := tag.EndServer.Get(ev); sid != "" {
+ i.State.dropServer(sid)
+ }
+ if s := cache.KeyUpdateSession.Get(ev); s != nil {
+ if c := i.State.Client(s.ID()); c != nil {
+ c.DebugAddress = tag.DebugAddress.Get(ev)
+ c.Logfile = tag.Logfile.Get(ev)
+ c.ServerID = tag.ServerID.Get(ev)
+ c.GoplsPath = tag.GoplsPath.Get(ev)
+ }
+ }
+ }
return ctx
}
metrics := metric.Config{}
@@ -601,7 +631,7 @@ var mainTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
Views
{{range .State.Views}}- {{.Name}} is {{template "viewlink" .ID}} from {{template "sessionlink" .Session.ID}} in {{.Folder}}
{{end}}
Clients
-{{range .State.Clients}}- {{template "clientlink" .ID}}
{{end}}
+{{range .State.Clients}}- {{template "clientlink" .Session.ID}}
{{end}}
Servers
{{range .State.Servers}}- {{template "serverlink" .ID}}
{{end}}
{{end}}
@@ -662,7 +692,7 @@ var cacheTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
`))
var clientTmpl = template.Must(template.Must(baseTemplate.Clone()).Parse(`
-{{define "title"}}Client {{.ID}}{{end}}
+{{define "title"}}Client {{.Session.ID}}{{end}}
{{define "body"}}
Using session: {{template "sessionlink" .Session.ID}}
{{if .DebugAddress}}Debug this client at: {{localAddress .DebugAddress}}
{{end}}
diff --git a/internal/lsp/debug/tag/tag.go b/internal/lsp/debug/tag/tag.go
index 7222f5a389..e37419e496 100644
--- a/internal/lsp/debug/tag/tag.go
+++ b/internal/lsp/debug/tag/tag.go
@@ -32,6 +32,15 @@ var (
Port = keys.NewInt("port", "")
Type = keys.New("type", "")
HoverKind = keys.NewString("hoverkind", "")
+
+ NewServer = keys.NewString("new_server", "A new server was added")
+ EndServer = keys.NewString("end_server", "A server was shut down")
+
+ ServerID = keys.NewString("server", "The server ID an event is related to")
+ Logfile = keys.NewString("logfile", "")
+ DebugAddress = keys.NewString("debug_address", "")
+ GoplsPath = keys.NewString("gopls_path", "")
+ ClientID = keys.NewString("client_id", "")
)
var (
diff --git a/internal/lsp/lsprpc/lsprpc.go b/internal/lsp/lsprpc/lsprpc.go
index c216e94817..d1d0d8839c 100644
--- a/internal/lsp/lsprpc/lsprpc.go
+++ b/internal/lsp/lsprpc/lsprpc.go
@@ -22,6 +22,7 @@ import (
"golang.org/x/tools/internal/lsp"
"golang.org/x/tools/internal/lsp/cache"
"golang.org/x/tools/internal/lsp/debug"
+ "golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/lsp/protocol"
)
@@ -30,7 +31,7 @@ import (
const AutoNetwork = "auto"
// Unique identifiers for client/server.
-var clientIndex, serverIndex int64
+var serverIndex int64
// The StreamServer type is a jsonrpc2.StreamServer that handles incoming
// streams as a new LSP session, using a shared cache.
@@ -51,15 +52,8 @@ func NewStreamServer(cache *cache.Cache) *StreamServer {
// ServeStream implements the jsonrpc2.StreamServer interface, by handling
// incoming streams using a new lsp server.
func (s *StreamServer) ServeStream(ctx context.Context, conn jsonrpc2.Conn) error {
- id := strconv.FormatInt(atomic.AddInt64(&clientIndex, 1), 10)
-
client := protocol.ClientDispatcher(conn)
session := s.cache.NewSession(ctx)
- dc := &debug.Client{ID: id, Session: session}
- if di := debug.GetInstance(ctx); di != nil {
- di.State.AddClient(dc)
- defer di.State.DropClient(id)
- }
server := s.serverForTest
if server == nil {
server = lsp.NewServer(session, client)
@@ -80,7 +74,7 @@ func (s *StreamServer) ServeStream(ctx context.Context, conn jsonrpc2.Conn) erro
ctx = protocol.WithClient(ctx, client)
conn.Go(ctx,
protocol.Handlers(
- handshaker(dc, executable,
+ handshaker(session, executable,
protocol.ServerHandler(server,
jsonrpc2.MethodNotFound))))
<-conn.Done()
@@ -205,7 +199,6 @@ func (f *Forwarder) ServeStream(ctx context.Context, clientConn jsonrpc2.Conn) e
// Do a handshake with the server instance to exchange debug information.
index := atomic.AddInt64(&serverIndex, 1)
serverID := strconv.FormatInt(index, 10)
- di := debug.GetInstance(ctx)
var (
hreq = handshakeRequest{
ServerID: serverID,
@@ -213,7 +206,7 @@ func (f *Forwarder) ServeStream(ctx context.Context, clientConn jsonrpc2.Conn) e
}
hresp handshakeResponse
)
- if di != nil {
+ if di := debug.GetInstance(ctx); di != nil {
hreq.Logfile = di.Logfile
hreq.DebugAddr = di.ListenedDebugAddress
}
@@ -223,15 +216,13 @@ func (f *Forwarder) ServeStream(ctx context.Context, clientConn jsonrpc2.Conn) e
if hresp.GoplsPath != f.goplsPath {
event.Error(ctx, "", fmt.Errorf("forwarder: gopls path mismatch: forwarder is %q, remote is %q", f.goplsPath, hresp.GoplsPath))
}
- if di != nil {
- di.State.AddServer(&debug.Server{
- ID: serverID,
- Logfile: hresp.Logfile,
- DebugAddress: hresp.DebugAddr,
- GoplsPath: hresp.GoplsPath,
- ClientID: hresp.ClientID,
- })
- }
+ event.Log(ctx, "New server",
+ tag.NewServer.Of(serverID),
+ tag.Logfile.Of(hresp.Logfile),
+ tag.DebugAddress.Of(hresp.DebugAddr),
+ tag.GoplsPath.Of(hresp.GoplsPath),
+ tag.ClientID.Of(hresp.SessionID),
+ )
clientConn.Go(ctx,
protocol.Handlers(
protocol.ServerHandler(server,
@@ -346,8 +337,6 @@ type handshakeRequest struct {
// A handshakeResponse is returned by the LSP server to tell the LSP client
// information about its session.
type handshakeResponse struct {
- // ClientID is the ID of the client as seen on the server.
- ClientID string `json:"clientID"`
// SessionID is the server session associated with the client.
SessionID string `json:"sessionID"`
// Logfile is the location of the server logs.
@@ -363,7 +352,6 @@ type handshakeResponse struct {
// that it looks similar to handshakeResposne, but in fact 'Logfile' and
// 'DebugAddr' now refer to the client.
type ClientSession struct {
- ClientID string `json:"clientID"`
SessionID string `json:"sessionID"`
Logfile string `json:"logfile"`
DebugAddr string `json:"debugAddr"`
@@ -385,7 +373,7 @@ const (
sessionsMethod = "gopls/sessions"
)
-func handshaker(client *debug.Client, goplsPath string, handler jsonrpc2.Handler) jsonrpc2.Handler {
+func handshaker(session *cache.Session, goplsPath string, handler jsonrpc2.Handler) jsonrpc2.Handler {
return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error {
switch r.Method() {
case handshakeMethod:
@@ -394,13 +382,15 @@ func handshaker(client *debug.Client, goplsPath string, handler jsonrpc2.Handler
sendError(ctx, reply, err)
return nil
}
- client.DebugAddress = req.DebugAddr
- client.Logfile = req.Logfile
- client.ServerID = req.ServerID
- client.GoplsPath = req.GoplsPath
+ event.Log(ctx, "Handshake session update",
+ cache.KeyUpdateSession.Of(session),
+ tag.DebugAddress.Of(req.DebugAddr),
+ tag.Logfile.Of(req.Logfile),
+ tag.ServerID.Of(req.ServerID),
+ tag.GoplsPath.Of(req.GoplsPath),
+ )
resp := handshakeResponse{
- ClientID: client.ID,
- SessionID: client.Session.ID(),
+ SessionID: session.ID(),
GoplsPath: goplsPath,
}
if di := debug.GetInstance(ctx); di != nil {
@@ -412,15 +402,13 @@ func handshaker(client *debug.Client, goplsPath string, handler jsonrpc2.Handler
case sessionsMethod:
resp := ServerState{
GoplsPath: goplsPath,
- CurrentClientID: client.ID,
+ CurrentClientID: session.ID(),
}
- //TODO: this should not need access to the debug information
if di := debug.GetInstance(ctx); di != nil {
resp.Logfile = di.Logfile
resp.DebugAddr = di.ListenedDebugAddress
for _, c := range di.State.Clients() {
resp.Clients = append(resp.Clients, ClientSession{
- ClientID: c.ID,
SessionID: c.Session.ID(),
Logfile: c.Logfile,
DebugAddr: c.DebugAddress,