1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package lsprpc implements a jsonrpc2.StreamServer that may be used to
6 // serve the LSP on a jsonrpc2 channel.
20 "golang.org/x/tools/internal/event"
21 "golang.org/x/tools/internal/gocommand"
22 "golang.org/x/tools/internal/jsonrpc2"
23 "golang.org/x/tools/internal/lsp"
24 "golang.org/x/tools/internal/lsp/cache"
25 "golang.org/x/tools/internal/lsp/debug"
26 "golang.org/x/tools/internal/lsp/debug/tag"
27 "golang.org/x/tools/internal/lsp/protocol"
28 errors "golang.org/x/xerrors"
31 // AutoNetwork is the pseudo network type used to signal that gopls should use
32 // automatic discovery to resolve a remote address.
33 const AutoNetwork = "auto"
35 // Unique identifiers for client/server.
38 // The StreamServer type is a jsonrpc2.StreamServer that handles incoming
39 // streams as a new LSP session, using a shared cache.
40 type StreamServer struct {
42 // daemon controls whether or not to log new connections.
45 // serverForTest may be set to a test fake for testing.
46 serverForTest protocol.Server
49 // NewStreamServer creates a StreamServer using the shared cache. If
50 // withTelemetry is true, each session is instrumented with telemetry that
51 // records RPC statistics.
52 func NewStreamServer(cache *cache.Cache, daemon bool) *StreamServer {
53 return &StreamServer{cache: cache, daemon: daemon}
56 // ServeStream implements the jsonrpc2.StreamServer interface, by handling
57 // incoming streams using a new lsp server.
58 func (s *StreamServer) ServeStream(ctx context.Context, conn jsonrpc2.Conn) error {
59 client := protocol.ClientDispatcher(conn)
60 session := s.cache.NewSession(ctx)
61 server := s.serverForTest
63 server = lsp.NewServer(session, client)
65 // Clients may or may not send a shutdown message. Make sure the server is
67 // TODO(rFindley): this shutdown should perhaps be on a disconnected context.
69 if err := server.Shutdown(ctx); err != nil {
70 event.Error(ctx, "error shutting down", err)
73 executable, err := os.Executable()
75 log.Printf("error getting gopls path: %v", err)
78 ctx = protocol.WithClient(ctx, client)
81 handshaker(session, executable, s.daemon,
82 protocol.ServerHandler(server,
83 jsonrpc2.MethodNotFound))))
85 log.Printf("Session %s: connected", session.ID())
86 defer log.Printf("Session %s: exited", session.ID())
92 // A Forwarder is a jsonrpc2.StreamServer that handles an LSP stream by
93 // forwarding it to a remote. This is used when the gopls process started by
94 // the editor is in the `-remote` mode, which means it finds and connects to a
95 // separate gopls daemon. In these cases, we still want the forwarder gopls to
96 // be instrumented with telemetry, and want to be able to in some cases hijack
97 // the jsonrpc2 connection with the daemon.
98 type Forwarder struct {
101 // goplsPath is the path to the current executing gopls binary.
104 // configuration for the auto-started gopls remote.
105 remoteConfig remoteConfig
108 type remoteConfig struct {
110 listenTimeout time.Duration
114 // A RemoteOption configures the behavior of the auto-started remote.
115 type RemoteOption interface {
119 // RemoteDebugAddress configures the address used by the auto-started Gopls daemon
120 // for serving debug information.
121 type RemoteDebugAddress string
123 func (d RemoteDebugAddress) set(cfg *remoteConfig) {
124 cfg.debug = string(d)
127 // RemoteListenTimeout configures the amount of time the auto-started gopls
128 // daemon will wait with no client connections before shutting down.
129 type RemoteListenTimeout time.Duration
131 func (d RemoteListenTimeout) set(cfg *remoteConfig) {
132 cfg.listenTimeout = time.Duration(d)
135 // RemoteLogfile configures the logfile location for the auto-started gopls
137 type RemoteLogfile string
139 func (l RemoteLogfile) set(cfg *remoteConfig) {
140 cfg.logfile = string(l)
143 func defaultRemoteConfig() remoteConfig {
145 listenTimeout: 1 * time.Minute,
149 // NewForwarder creates a new Forwarder, ready to forward connections to the
150 // remote server specified by network and addr.
151 func NewForwarder(network, addr string, opts ...RemoteOption) *Forwarder {
152 gp, err := os.Executable()
154 log.Printf("error getting gopls path for forwarder: %v", err)
158 rcfg := defaultRemoteConfig()
159 for _, opt := range opts {
172 // QueryServerState queries the server state of the current server.
173 func QueryServerState(ctx context.Context, network, address string) (*ServerState, error) {
174 if network == AutoNetwork {
175 gp, err := os.Executable()
177 return nil, errors.Errorf("getting gopls path: %w", err)
179 network, address = autoNetworkAddress(gp, address)
181 netConn, err := net.DialTimeout(network, address, 5*time.Second)
183 return nil, errors.Errorf("dialing remote: %w", err)
185 serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn))
186 serverConn.Go(ctx, jsonrpc2.MethodNotFound)
187 var state ServerState
188 if err := protocol.Call(ctx, serverConn, sessionsMethod, nil, &state); err != nil {
189 return nil, errors.Errorf("querying server state: %w", err)
194 // ServeStream dials the forwarder remote and binds the remote to serve the LSP
195 // on the incoming stream.
196 func (f *Forwarder) ServeStream(ctx context.Context, clientConn jsonrpc2.Conn) error {
197 client := protocol.ClientDispatcher(clientConn)
199 netConn, err := f.connectToRemote(ctx)
201 return errors.Errorf("forwarder: connecting to remote: %w", err)
203 serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn))
204 server := protocol.ServerDispatcher(serverConn)
206 // Forward between connections.
209 protocol.ClientHandler(client,
210 jsonrpc2.MethodNotFound)))
211 // Don't run the clientConn yet, so that we can complete the handshake before
212 // processing any client messages.
214 // Do a handshake with the server instance to exchange debug information.
215 index := atomic.AddInt64(&serverIndex, 1)
216 serverID := strconv.FormatInt(index, 10)
218 hreq = handshakeRequest{
220 GoplsPath: f.goplsPath,
222 hresp handshakeResponse
224 if di := debug.GetInstance(ctx); di != nil {
225 hreq.Logfile = di.Logfile
226 hreq.DebugAddr = di.ListenedDebugAddress
228 if err := protocol.Call(ctx, serverConn, handshakeMethod, hreq, &hresp); err != nil {
229 // TODO(rfindley): at some point in the future we should return an error
230 // here. Handshakes have become functional in nature.
231 event.Error(ctx, "forwarder: gopls handshake failed", err)
233 if hresp.GoplsPath != f.goplsPath {
234 event.Error(ctx, "", fmt.Errorf("forwarder: gopls path mismatch: forwarder is %q, remote is %q", f.goplsPath, hresp.GoplsPath))
236 event.Log(ctx, "New server",
237 tag.NewServer.Of(serverID),
238 tag.Logfile.Of(hresp.Logfile),
239 tag.DebugAddress.Of(hresp.DebugAddr),
240 tag.GoplsPath.Of(hresp.GoplsPath),
241 tag.ClientID.Of(hresp.SessionID),
246 protocol.ServerHandler(server,
247 jsonrpc2.MethodNotFound))))
250 case <-serverConn.Done():
252 case <-clientConn.Done():
257 if serverConn.Err() != nil {
258 err = errors.Errorf("remote disconnected: %v", err)
259 } else if clientConn.Err() != nil {
260 err = errors.Errorf("client disconnected: %v", err)
262 event.Log(ctx, fmt.Sprintf("forwarder: exited with error: %v", err))
266 func (f *Forwarder) connectToRemote(ctx context.Context) (net.Conn, error) {
267 return connectToRemote(ctx, f.network, f.addr, f.goplsPath, f.remoteConfig)
270 func ConnectToRemote(ctx context.Context, network, addr string, opts ...RemoteOption) (net.Conn, error) {
271 rcfg := defaultRemoteConfig()
272 for _, opt := range opts {
275 // This is not strictly necessary, as it won't be used if not connecting to
276 // the 'auto' remote.
277 goplsPath, err := os.Executable()
279 return nil, fmt.Errorf("unable to resolve gopls path: %v", err)
281 return connectToRemote(ctx, network, addr, goplsPath, rcfg)
284 func connectToRemote(ctx context.Context, inNetwork, inAddr, goplsPath string, rcfg remoteConfig) (net.Conn, error) {
288 network, address = inNetwork, inAddr
290 if inNetwork == AutoNetwork {
291 // f.network is overloaded to support a concept of 'automatic' addresses,
292 // which signals that the gopls remote address should be automatically
294 // So we need to resolve a real network and address here.
295 network, address = autoNetworkAddress(goplsPath, inAddr)
297 // Attempt to verify that we own the remote. This is imperfect, but if we can
298 // determine that the remote is owned by a different user, we should fail.
299 ok, err := verifyRemoteOwnership(network, address)
301 // If the ownership check itself failed, we fail open but log an error to
303 event.Error(ctx, "unable to check daemon socket owner, failing open", err)
305 // We succesfully checked that the socket is not owned by us, we fail
307 return nil, fmt.Errorf("socket %q is owned by a different user", address)
309 const dialTimeout = 1 * time.Second
310 // Try dialing our remote once, in case it is already running.
311 netConn, err = net.DialTimeout(network, address, dialTimeout)
315 // If our remote is on the 'auto' network, start it if it doesn't exist.
316 if inNetwork == AutoNetwork {
318 return nil, fmt.Errorf("cannot auto-start remote: gopls path is unknown")
320 if network == "unix" {
321 // Sometimes the socketfile isn't properly cleaned up when gopls shuts
322 // down. Since we have already tried and failed to dial this address, it
323 // should *usually* be safe to remove the socket before binding to the
325 // TODO(rfindley): there is probably a race here if multiple gopls
326 // instances are simultaneously starting up.
327 if _, err := os.Stat(address); err == nil {
328 if err := os.Remove(address); err != nil {
329 return nil, errors.Errorf("removing remote socket file: %w", err)
333 args := []string{"serve",
334 "-listen", fmt.Sprintf(`%s;%s`, network, address),
335 "-listen.timeout", rcfg.listenTimeout.String(),
337 if rcfg.logfile != "" {
338 args = append(args, "-logfile", rcfg.logfile)
340 if rcfg.debug != "" {
341 args = append(args, "-debug", rcfg.debug)
343 if err := startRemote(goplsPath, args...); err != nil {
344 return nil, errors.Errorf("startRemote(%q, %v): %w", goplsPath, args, err)
349 // It can take some time for the newly started server to bind to our address,
350 // so we retry for a bit.
351 for retry := 0; retry < retries; retry++ {
352 startDial := time.Now()
353 netConn, err = net.DialTimeout(network, address, dialTimeout)
357 event.Log(ctx, fmt.Sprintf("failed attempt #%d to connect to remote: %v\n", retry+2, err))
358 // In case our failure was a fast-failure, ensure we wait at least
359 // f.dialTimeout before trying again.
360 if retry != retries-1 {
361 time.Sleep(dialTimeout - time.Since(startDial))
364 return nil, errors.Errorf("dialing remote: %w", err)
367 // forwarderHandler intercepts 'exit' messages to prevent the shared gopls
368 // instance from exiting. In the future it may also intercept 'shutdown' to
369 // provide more graceful shutdown of the client connection.
370 func forwarderHandler(handler jsonrpc2.Handler) jsonrpc2.Handler {
371 return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error {
372 // The gopls workspace environment defaults to the process environment in
373 // which gopls daemon was started. To avoid discrepancies in Go environment
374 // between the editor and daemon, inject any unset variables in `go env`
375 // into the options sent by initialize.
377 // See also golang.org/issue/37830.
378 if r.Method() == "initialize" {
379 if newr, err := addGoEnvToInitializeRequest(ctx, r); err == nil {
382 log.Printf("unable to add local env to initialize request: %v", err)
385 return handler(ctx, reply, r)
389 // addGoEnvToInitializeRequest builds a new initialize request in which we set
390 // any environment variables output by `go env` and not already present in the
393 // It returns an error if r is not an initialize requst, or is otherwise
395 func addGoEnvToInitializeRequest(ctx context.Context, r jsonrpc2.Request) (jsonrpc2.Request, error) {
396 var params protocol.ParamInitialize
397 if err := json.Unmarshal(r.Params(), ¶ms); err != nil {
400 var opts map[string]interface{}
401 switch v := params.InitializationOptions.(type) {
403 opts = make(map[string]interface{})
404 case map[string]interface{}:
407 return nil, fmt.Errorf("unexpected type for InitializationOptions: %T", v)
409 envOpt, ok := opts["env"]
411 envOpt = make(map[string]interface{})
413 env, ok := envOpt.(map[string]interface{})
415 return nil, fmt.Errorf(`env option is %T, expected a map`, envOpt)
417 goenv, err := getGoEnv(ctx, env)
421 for govar, value := range goenv {
425 params.InitializationOptions = opts
426 call, ok := r.(*jsonrpc2.Call)
428 return nil, fmt.Errorf("%T is not a *jsonrpc2.Call", r)
430 return jsonrpc2.NewCall(call.ID(), "initialize", params)
433 func getGoEnv(ctx context.Context, env map[string]interface{}) (map[string]string, error) {
435 for k, v := range env {
436 runEnv = append(runEnv, fmt.Sprintf("%s=%s", k, v))
438 runner := gocommand.Runner{}
439 output, err := runner.Run(ctx, gocommand.Invocation{
441 Args: []string{"-json"},
447 envmap := make(map[string]string)
448 if err := json.Unmarshal(output.Bytes(), &envmap); err != nil {
454 // A handshakeRequest identifies a client to the LSP server.
455 type handshakeRequest struct {
456 // ServerID is the ID of the server on the client. This should usually be 0.
457 ServerID string `json:"serverID"`
458 // Logfile is the location of the clients log file.
459 Logfile string `json:"logfile"`
460 // DebugAddr is the client debug address.
461 DebugAddr string `json:"debugAddr"`
462 // GoplsPath is the path to the Gopls binary running the current client
464 GoplsPath string `json:"goplsPath"`
467 // A handshakeResponse is returned by the LSP server to tell the LSP client
468 // information about its session.
469 type handshakeResponse struct {
470 // SessionID is the server session associated with the client.
471 SessionID string `json:"sessionID"`
472 // Logfile is the location of the server logs.
473 Logfile string `json:"logfile"`
474 // DebugAddr is the server debug address.
475 DebugAddr string `json:"debugAddr"`
476 // GoplsPath is the path to the Gopls binary running the current server
478 GoplsPath string `json:"goplsPath"`
481 // ClientSession identifies a current client LSP session on the server. Note
482 // that it looks similar to handshakeResposne, but in fact 'Logfile' and
483 // 'DebugAddr' now refer to the client.
484 type ClientSession struct {
485 SessionID string `json:"sessionID"`
486 Logfile string `json:"logfile"`
487 DebugAddr string `json:"debugAddr"`
490 // ServerState holds information about the gopls daemon process, including its
491 // debug information and debug information of all of its current connected
493 type ServerState struct {
494 Logfile string `json:"logfile"`
495 DebugAddr string `json:"debugAddr"`
496 GoplsPath string `json:"goplsPath"`
497 CurrentClientID string `json:"currentClientID"`
498 Clients []ClientSession `json:"clients"`
502 handshakeMethod = "gopls/handshake"
503 sessionsMethod = "gopls/sessions"
506 func handshaker(session *cache.Session, goplsPath string, logHandshakes bool, handler jsonrpc2.Handler) jsonrpc2.Handler {
507 return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error {
509 case handshakeMethod:
510 // We log.Printf in this handler, rather than event.Log when we want logs
511 // to go to the daemon log rather than being reflected back to the
513 var req handshakeRequest
514 if err := json.Unmarshal(r.Params(), &req); err != nil {
516 log.Printf("Error processing handshake for session %s: %v", session.ID(), err)
518 sendError(ctx, reply, err)
522 log.Printf("Session %s: got handshake. Logfile: %q, Debug addr: %q", session.ID(), req.Logfile, req.DebugAddr)
524 event.Log(ctx, "Handshake session update",
525 cache.KeyUpdateSession.Of(session),
526 tag.DebugAddress.Of(req.DebugAddr),
527 tag.Logfile.Of(req.Logfile),
528 tag.ServerID.Of(req.ServerID),
529 tag.GoplsPath.Of(req.GoplsPath),
531 resp := handshakeResponse{
532 SessionID: session.ID(),
533 GoplsPath: goplsPath,
535 if di := debug.GetInstance(ctx); di != nil {
536 resp.Logfile = di.Logfile
537 resp.DebugAddr = di.ListenedDebugAddress
540 return reply(ctx, resp, nil)
543 GoplsPath: goplsPath,
544 CurrentClientID: session.ID(),
546 if di := debug.GetInstance(ctx); di != nil {
547 resp.Logfile = di.Logfile
548 resp.DebugAddr = di.ListenedDebugAddress
549 for _, c := range di.State.Clients() {
550 resp.Clients = append(resp.Clients, ClientSession{
551 SessionID: c.Session.ID(),
553 DebugAddr: c.DebugAddress,
557 return reply(ctx, resp, nil)
559 return handler(ctx, reply, r)
563 func sendError(ctx context.Context, reply jsonrpc2.Replier, err error) {
564 err = errors.Errorf("%v: %w", err, jsonrpc2.ErrParse)
565 if err := reply(ctx, nil, err); err != nil {
566 event.Error(ctx, "", err)