internal/lsp: refactor LSP server instantiation
Previously, the process of instantiating and running the LSP server was
sharded across the lsp, protocol, and cmd packages, and this resulted in
some APIs that are hard to work with. For example, it's hard to guess
the difference between lsp.NewClientServer, lsp.NewServer,
protocol.NewServer (which returns a client), and protocol.NewClient
(which returns a server).
This change reorganizes Server instantiation as follows:
+ The lsp.Server is now purely an implementation of the protocol.Server
interface. It is no longer responsible for installing itself into the
jsonrpc2 Stream, nor for running itself.
+ A new package 'lsprpc' is added, to implement the logic of binding an
incoming connection to an LSP server session. This is put in a
separate package for lack of a clear home: it didn't really
philosophically belong in any of the lsp, cmd, or protocol packages.
We can perhaps move it to cmd in the future, but I'd like to keep it
as a separate package while I develop request forwarding.
simplified import graph:
jsonrpc2 ⭠ lsprpc ⭠ cmd
⭩ ⭦
lsp (t.b.d. client tests)
⭩ ⭨
protocol source
+ The jsonrpc2 package is extended to have a minimal API for running a
'StreamServer': something analogous to an HTTP server that listens
for new connections and delegates to a handler (but we couldn't use
the word 'Handler' for this delegate as it was already taken).
After these changes, I hope that the concerns of "serving the LSP",
"serving jsonrpc2", and "installing the LSP on jsonrpc2" are more
logically organized, though one legitimate criticism is that the word
'Server' is still heavily overloaded.
This change prepares a subsequent change which hijacks the jsonrpc2
connection when forwarding messages to a shared gopls instance.
To test this change, the following improvements are made:
+ A servertest package is added to make it easier to run a test against
an in-process jsonrpc2 server. For now, this uses TCP but it could
easily be modified to use io.Pipe.
+ cmd tests are updated to use the servertest package. Unfortunately it
wasn't yet possible to eliminate the concept of `remote=internal` in
favor of just using multiple sessions, because view initialization
involves calling both `go env` and `packages.Load`, which slow down
session startup significantly. See also golang.org/issue/35968.
Instead, the syntax for `-remote=internal` is modified to be
`-remote=internal@127.0.0.1:12345`.
+ An additional test for request cancellation is added for the
sessionserver package. This test uncovered a bug: when calling
Canceller.Cancel, we were using id rather than &id, which resulted in
incorrect json serialization (as only the pointer receiver implements
the json.Marshaller interface).
Updates golang/go#34111
Change-Id: I75c219df634348cdf53a9e57839b98588311a9ef
Reviewed-on: https://go-review.googlesource.com/c/tools/+/215742
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-01-21 17:34:50 -07:00
|
|
|
// 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 lsprpc implements a jsonrpc2.StreamServer that may be used to
|
|
|
|
// serve the LSP on a jsonrpc2 channel.
|
|
|
|
package lsprpc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-02-19 08:18:21 -07:00
|
|
|
"encoding/json"
|
2020-02-06 17:50:37 -07:00
|
|
|
"fmt"
|
2020-03-07 19:28:21 -07:00
|
|
|
"log"
|
2020-02-06 17:50:37 -07:00
|
|
|
"net"
|
2020-02-09 12:44:03 -07:00
|
|
|
"os"
|
2020-02-19 08:18:21 -07:00
|
|
|
"strconv"
|
|
|
|
"sync/atomic"
|
2020-02-10 09:34:13 -07:00
|
|
|
"time"
|
internal/lsp: refactor LSP server instantiation
Previously, the process of instantiating and running the LSP server was
sharded across the lsp, protocol, and cmd packages, and this resulted in
some APIs that are hard to work with. For example, it's hard to guess
the difference between lsp.NewClientServer, lsp.NewServer,
protocol.NewServer (which returns a client), and protocol.NewClient
(which returns a server).
This change reorganizes Server instantiation as follows:
+ The lsp.Server is now purely an implementation of the protocol.Server
interface. It is no longer responsible for installing itself into the
jsonrpc2 Stream, nor for running itself.
+ A new package 'lsprpc' is added, to implement the logic of binding an
incoming connection to an LSP server session. This is put in a
separate package for lack of a clear home: it didn't really
philosophically belong in any of the lsp, cmd, or protocol packages.
We can perhaps move it to cmd in the future, but I'd like to keep it
as a separate package while I develop request forwarding.
simplified import graph:
jsonrpc2 ⭠ lsprpc ⭠ cmd
⭩ ⭦
lsp (t.b.d. client tests)
⭩ ⭨
protocol source
+ The jsonrpc2 package is extended to have a minimal API for running a
'StreamServer': something analogous to an HTTP server that listens
for new connections and delegates to a handler (but we couldn't use
the word 'Handler' for this delegate as it was already taken).
After these changes, I hope that the concerns of "serving the LSP",
"serving jsonrpc2", and "installing the LSP on jsonrpc2" are more
logically organized, though one legitimate criticism is that the word
'Server' is still heavily overloaded.
This change prepares a subsequent change which hijacks the jsonrpc2
connection when forwarding messages to a shared gopls instance.
To test this change, the following improvements are made:
+ A servertest package is added to make it easier to run a test against
an in-process jsonrpc2 server. For now, this uses TCP but it could
easily be modified to use io.Pipe.
+ cmd tests are updated to use the servertest package. Unfortunately it
wasn't yet possible to eliminate the concept of `remote=internal` in
favor of just using multiple sessions, because view initialization
involves calling both `go env` and `packages.Load`, which slow down
session startup significantly. See also golang.org/issue/35968.
Instead, the syntax for `-remote=internal` is modified to be
`-remote=internal@127.0.0.1:12345`.
+ An additional test for request cancellation is added for the
sessionserver package. This test uncovered a bug: when calling
Canceller.Cancel, we were using id rather than &id, which resulted in
incorrect json serialization (as only the pointer receiver implements
the json.Marshaller interface).
Updates golang/go#34111
Change-Id: I75c219df634348cdf53a9e57839b98588311a9ef
Reviewed-on: https://go-review.googlesource.com/c/tools/+/215742
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-01-21 17:34:50 -07:00
|
|
|
|
2020-04-17 07:32:56 -06:00
|
|
|
"golang.org/x/tools/internal/event"
|
2020-05-14 21:15:12 -06:00
|
|
|
"golang.org/x/tools/internal/gocommand"
|
internal/lsp: refactor LSP server instantiation
Previously, the process of instantiating and running the LSP server was
sharded across the lsp, protocol, and cmd packages, and this resulted in
some APIs that are hard to work with. For example, it's hard to guess
the difference between lsp.NewClientServer, lsp.NewServer,
protocol.NewServer (which returns a client), and protocol.NewClient
(which returns a server).
This change reorganizes Server instantiation as follows:
+ The lsp.Server is now purely an implementation of the protocol.Server
interface. It is no longer responsible for installing itself into the
jsonrpc2 Stream, nor for running itself.
+ A new package 'lsprpc' is added, to implement the logic of binding an
incoming connection to an LSP server session. This is put in a
separate package for lack of a clear home: it didn't really
philosophically belong in any of the lsp, cmd, or protocol packages.
We can perhaps move it to cmd in the future, but I'd like to keep it
as a separate package while I develop request forwarding.
simplified import graph:
jsonrpc2 ⭠ lsprpc ⭠ cmd
⭩ ⭦
lsp (t.b.d. client tests)
⭩ ⭨
protocol source
+ The jsonrpc2 package is extended to have a minimal API for running a
'StreamServer': something analogous to an HTTP server that listens
for new connections and delegates to a handler (but we couldn't use
the word 'Handler' for this delegate as it was already taken).
After these changes, I hope that the concerns of "serving the LSP",
"serving jsonrpc2", and "installing the LSP on jsonrpc2" are more
logically organized, though one legitimate criticism is that the word
'Server' is still heavily overloaded.
This change prepares a subsequent change which hijacks the jsonrpc2
connection when forwarding messages to a shared gopls instance.
To test this change, the following improvements are made:
+ A servertest package is added to make it easier to run a test against
an in-process jsonrpc2 server. For now, this uses TCP but it could
easily be modified to use io.Pipe.
+ cmd tests are updated to use the servertest package. Unfortunately it
wasn't yet possible to eliminate the concept of `remote=internal` in
favor of just using multiple sessions, because view initialization
involves calling both `go env` and `packages.Load`, which slow down
session startup significantly. See also golang.org/issue/35968.
Instead, the syntax for `-remote=internal` is modified to be
`-remote=internal@127.0.0.1:12345`.
+ An additional test for request cancellation is added for the
sessionserver package. This test uncovered a bug: when calling
Canceller.Cancel, we were using id rather than &id, which resulted in
incorrect json serialization (as only the pointer receiver implements
the json.Marshaller interface).
Updates golang/go#34111
Change-Id: I75c219df634348cdf53a9e57839b98588311a9ef
Reviewed-on: https://go-review.googlesource.com/c/tools/+/215742
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-01-21 17:34:50 -07:00
|
|
|
"golang.org/x/tools/internal/jsonrpc2"
|
|
|
|
"golang.org/x/tools/internal/lsp"
|
2020-02-18 18:59:37 -07:00
|
|
|
"golang.org/x/tools/internal/lsp/cache"
|
2020-02-19 08:18:21 -07:00
|
|
|
"golang.org/x/tools/internal/lsp/debug"
|
2020-06-03 15:06:45 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/debug/tag"
|
internal/lsp: refactor LSP server instantiation
Previously, the process of instantiating and running the LSP server was
sharded across the lsp, protocol, and cmd packages, and this resulted in
some APIs that are hard to work with. For example, it's hard to guess
the difference between lsp.NewClientServer, lsp.NewServer,
protocol.NewServer (which returns a client), and protocol.NewClient
(which returns a server).
This change reorganizes Server instantiation as follows:
+ The lsp.Server is now purely an implementation of the protocol.Server
interface. It is no longer responsible for installing itself into the
jsonrpc2 Stream, nor for running itself.
+ A new package 'lsprpc' is added, to implement the logic of binding an
incoming connection to an LSP server session. This is put in a
separate package for lack of a clear home: it didn't really
philosophically belong in any of the lsp, cmd, or protocol packages.
We can perhaps move it to cmd in the future, but I'd like to keep it
as a separate package while I develop request forwarding.
simplified import graph:
jsonrpc2 ⭠ lsprpc ⭠ cmd
⭩ ⭦
lsp (t.b.d. client tests)
⭩ ⭨
protocol source
+ The jsonrpc2 package is extended to have a minimal API for running a
'StreamServer': something analogous to an HTTP server that listens
for new connections and delegates to a handler (but we couldn't use
the word 'Handler' for this delegate as it was already taken).
After these changes, I hope that the concerns of "serving the LSP",
"serving jsonrpc2", and "installing the LSP on jsonrpc2" are more
logically organized, though one legitimate criticism is that the word
'Server' is still heavily overloaded.
This change prepares a subsequent change which hijacks the jsonrpc2
connection when forwarding messages to a shared gopls instance.
To test this change, the following improvements are made:
+ A servertest package is added to make it easier to run a test against
an in-process jsonrpc2 server. For now, this uses TCP but it could
easily be modified to use io.Pipe.
+ cmd tests are updated to use the servertest package. Unfortunately it
wasn't yet possible to eliminate the concept of `remote=internal` in
favor of just using multiple sessions, because view initialization
involves calling both `go env` and `packages.Load`, which slow down
session startup significantly. See also golang.org/issue/35968.
Instead, the syntax for `-remote=internal` is modified to be
`-remote=internal@127.0.0.1:12345`.
+ An additional test for request cancellation is added for the
sessionserver package. This test uncovered a bug: when calling
Canceller.Cancel, we were using id rather than &id, which resulted in
incorrect json serialization (as only the pointer receiver implements
the json.Marshaller interface).
Updates golang/go#34111
Change-Id: I75c219df634348cdf53a9e57839b98588311a9ef
Reviewed-on: https://go-review.googlesource.com/c/tools/+/215742
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-01-21 17:34:50 -07:00
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
|
|
)
|
|
|
|
|
2020-02-18 10:47:19 -07:00
|
|
|
// AutoNetwork is the pseudo network type used to signal that gopls should use
|
|
|
|
// automatic discovery to resolve a remote address.
|
|
|
|
const AutoNetwork = "auto"
|
|
|
|
|
2020-03-09 11:22:56 -06:00
|
|
|
// Unique identifiers for client/server.
|
2020-06-03 15:06:45 -06:00
|
|
|
var serverIndex int64
|
2020-03-09 11:22:56 -06:00
|
|
|
|
internal/lsp: refactor LSP server instantiation
Previously, the process of instantiating and running the LSP server was
sharded across the lsp, protocol, and cmd packages, and this resulted in
some APIs that are hard to work with. For example, it's hard to guess
the difference between lsp.NewClientServer, lsp.NewServer,
protocol.NewServer (which returns a client), and protocol.NewClient
(which returns a server).
This change reorganizes Server instantiation as follows:
+ The lsp.Server is now purely an implementation of the protocol.Server
interface. It is no longer responsible for installing itself into the
jsonrpc2 Stream, nor for running itself.
+ A new package 'lsprpc' is added, to implement the logic of binding an
incoming connection to an LSP server session. This is put in a
separate package for lack of a clear home: it didn't really
philosophically belong in any of the lsp, cmd, or protocol packages.
We can perhaps move it to cmd in the future, but I'd like to keep it
as a separate package while I develop request forwarding.
simplified import graph:
jsonrpc2 ⭠ lsprpc ⭠ cmd
⭩ ⭦
lsp (t.b.d. client tests)
⭩ ⭨
protocol source
+ The jsonrpc2 package is extended to have a minimal API for running a
'StreamServer': something analogous to an HTTP server that listens
for new connections and delegates to a handler (but we couldn't use
the word 'Handler' for this delegate as it was already taken).
After these changes, I hope that the concerns of "serving the LSP",
"serving jsonrpc2", and "installing the LSP on jsonrpc2" are more
logically organized, though one legitimate criticism is that the word
'Server' is still heavily overloaded.
This change prepares a subsequent change which hijacks the jsonrpc2
connection when forwarding messages to a shared gopls instance.
To test this change, the following improvements are made:
+ A servertest package is added to make it easier to run a test against
an in-process jsonrpc2 server. For now, this uses TCP but it could
easily be modified to use io.Pipe.
+ cmd tests are updated to use the servertest package. Unfortunately it
wasn't yet possible to eliminate the concept of `remote=internal` in
favor of just using multiple sessions, because view initialization
involves calling both `go env` and `packages.Load`, which slow down
session startup significantly. See also golang.org/issue/35968.
Instead, the syntax for `-remote=internal` is modified to be
`-remote=internal@127.0.0.1:12345`.
+ An additional test for request cancellation is added for the
sessionserver package. This test uncovered a bug: when calling
Canceller.Cancel, we were using id rather than &id, which resulted in
incorrect json serialization (as only the pointer receiver implements
the json.Marshaller interface).
Updates golang/go#34111
Change-Id: I75c219df634348cdf53a9e57839b98588311a9ef
Reviewed-on: https://go-review.googlesource.com/c/tools/+/215742
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-01-21 17:34:50 -07:00
|
|
|
// The StreamServer type is a jsonrpc2.StreamServer that handles incoming
|
|
|
|
// streams as a new LSP session, using a shared cache.
|
|
|
|
type StreamServer struct {
|
2020-03-26 20:00:12 -06:00
|
|
|
cache *cache.Cache
|
2020-07-08 11:49:07 -06:00
|
|
|
// logConnections controls whether or not to log new connections.
|
|
|
|
logConnections bool
|
internal/lsp: refactor LSP server instantiation
Previously, the process of instantiating and running the LSP server was
sharded across the lsp, protocol, and cmd packages, and this resulted in
some APIs that are hard to work with. For example, it's hard to guess
the difference between lsp.NewClientServer, lsp.NewServer,
protocol.NewServer (which returns a client), and protocol.NewClient
(which returns a server).
This change reorganizes Server instantiation as follows:
+ The lsp.Server is now purely an implementation of the protocol.Server
interface. It is no longer responsible for installing itself into the
jsonrpc2 Stream, nor for running itself.
+ A new package 'lsprpc' is added, to implement the logic of binding an
incoming connection to an LSP server session. This is put in a
separate package for lack of a clear home: it didn't really
philosophically belong in any of the lsp, cmd, or protocol packages.
We can perhaps move it to cmd in the future, but I'd like to keep it
as a separate package while I develop request forwarding.
simplified import graph:
jsonrpc2 ⭠ lsprpc ⭠ cmd
⭩ ⭦
lsp (t.b.d. client tests)
⭩ ⭨
protocol source
+ The jsonrpc2 package is extended to have a minimal API for running a
'StreamServer': something analogous to an HTTP server that listens
for new connections and delegates to a handler (but we couldn't use
the word 'Handler' for this delegate as it was already taken).
After these changes, I hope that the concerns of "serving the LSP",
"serving jsonrpc2", and "installing the LSP on jsonrpc2" are more
logically organized, though one legitimate criticism is that the word
'Server' is still heavily overloaded.
This change prepares a subsequent change which hijacks the jsonrpc2
connection when forwarding messages to a shared gopls instance.
To test this change, the following improvements are made:
+ A servertest package is added to make it easier to run a test against
an in-process jsonrpc2 server. For now, this uses TCP but it could
easily be modified to use io.Pipe.
+ cmd tests are updated to use the servertest package. Unfortunately it
wasn't yet possible to eliminate the concept of `remote=internal` in
favor of just using multiple sessions, because view initialization
involves calling both `go env` and `packages.Load`, which slow down
session startup significantly. See also golang.org/issue/35968.
Instead, the syntax for `-remote=internal` is modified to be
`-remote=internal@127.0.0.1:12345`.
+ An additional test for request cancellation is added for the
sessionserver package. This test uncovered a bug: when calling
Canceller.Cancel, we were using id rather than &id, which resulted in
incorrect json serialization (as only the pointer receiver implements
the json.Marshaller interface).
Updates golang/go#34111
Change-Id: I75c219df634348cdf53a9e57839b98588311a9ef
Reviewed-on: https://go-review.googlesource.com/c/tools/+/215742
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-01-21 17:34:50 -07:00
|
|
|
|
2020-02-19 08:18:21 -07:00
|
|
|
// serverForTest may be set to a test fake for testing.
|
2020-02-19 08:17:48 -07:00
|
|
|
serverForTest protocol.Server
|
internal/lsp: refactor LSP server instantiation
Previously, the process of instantiating and running the LSP server was
sharded across the lsp, protocol, and cmd packages, and this resulted in
some APIs that are hard to work with. For example, it's hard to guess
the difference between lsp.NewClientServer, lsp.NewServer,
protocol.NewServer (which returns a client), and protocol.NewClient
(which returns a server).
This change reorganizes Server instantiation as follows:
+ The lsp.Server is now purely an implementation of the protocol.Server
interface. It is no longer responsible for installing itself into the
jsonrpc2 Stream, nor for running itself.
+ A new package 'lsprpc' is added, to implement the logic of binding an
incoming connection to an LSP server session. This is put in a
separate package for lack of a clear home: it didn't really
philosophically belong in any of the lsp, cmd, or protocol packages.
We can perhaps move it to cmd in the future, but I'd like to keep it
as a separate package while I develop request forwarding.
simplified import graph:
jsonrpc2 ⭠ lsprpc ⭠ cmd
⭩ ⭦
lsp (t.b.d. client tests)
⭩ ⭨
protocol source
+ The jsonrpc2 package is extended to have a minimal API for running a
'StreamServer': something analogous to an HTTP server that listens
for new connections and delegates to a handler (but we couldn't use
the word 'Handler' for this delegate as it was already taken).
After these changes, I hope that the concerns of "serving the LSP",
"serving jsonrpc2", and "installing the LSP on jsonrpc2" are more
logically organized, though one legitimate criticism is that the word
'Server' is still heavily overloaded.
This change prepares a subsequent change which hijacks the jsonrpc2
connection when forwarding messages to a shared gopls instance.
To test this change, the following improvements are made:
+ A servertest package is added to make it easier to run a test against
an in-process jsonrpc2 server. For now, this uses TCP but it could
easily be modified to use io.Pipe.
+ cmd tests are updated to use the servertest package. Unfortunately it
wasn't yet possible to eliminate the concept of `remote=internal` in
favor of just using multiple sessions, because view initialization
involves calling both `go env` and `packages.Load`, which slow down
session startup significantly. See also golang.org/issue/35968.
Instead, the syntax for `-remote=internal` is modified to be
`-remote=internal@127.0.0.1:12345`.
+ An additional test for request cancellation is added for the
sessionserver package. This test uncovered a bug: when calling
Canceller.Cancel, we were using id rather than &id, which resulted in
incorrect json serialization (as only the pointer receiver implements
the json.Marshaller interface).
Updates golang/go#34111
Change-Id: I75c219df634348cdf53a9e57839b98588311a9ef
Reviewed-on: https://go-review.googlesource.com/c/tools/+/215742
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-01-21 17:34:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewStreamServer creates a StreamServer using the shared cache. If
|
|
|
|
// withTelemetry is true, each session is instrumented with telemetry that
|
|
|
|
// records RPC statistics.
|
2020-07-08 11:49:07 -06:00
|
|
|
func NewStreamServer(cache *cache.Cache, logConnections bool) *StreamServer {
|
|
|
|
return &StreamServer{cache: cache, logConnections: logConnections}
|
internal/lsp: refactor LSP server instantiation
Previously, the process of instantiating and running the LSP server was
sharded across the lsp, protocol, and cmd packages, and this resulted in
some APIs that are hard to work with. For example, it's hard to guess
the difference between lsp.NewClientServer, lsp.NewServer,
protocol.NewServer (which returns a client), and protocol.NewClient
(which returns a server).
This change reorganizes Server instantiation as follows:
+ The lsp.Server is now purely an implementation of the protocol.Server
interface. It is no longer responsible for installing itself into the
jsonrpc2 Stream, nor for running itself.
+ A new package 'lsprpc' is added, to implement the logic of binding an
incoming connection to an LSP server session. This is put in a
separate package for lack of a clear home: it didn't really
philosophically belong in any of the lsp, cmd, or protocol packages.
We can perhaps move it to cmd in the future, but I'd like to keep it
as a separate package while I develop request forwarding.
simplified import graph:
jsonrpc2 ⭠ lsprpc ⭠ cmd
⭩ ⭦
lsp (t.b.d. client tests)
⭩ ⭨
protocol source
+ The jsonrpc2 package is extended to have a minimal API for running a
'StreamServer': something analogous to an HTTP server that listens
for new connections and delegates to a handler (but we couldn't use
the word 'Handler' for this delegate as it was already taken).
After these changes, I hope that the concerns of "serving the LSP",
"serving jsonrpc2", and "installing the LSP on jsonrpc2" are more
logically organized, though one legitimate criticism is that the word
'Server' is still heavily overloaded.
This change prepares a subsequent change which hijacks the jsonrpc2
connection when forwarding messages to a shared gopls instance.
To test this change, the following improvements are made:
+ A servertest package is added to make it easier to run a test against
an in-process jsonrpc2 server. For now, this uses TCP but it could
easily be modified to use io.Pipe.
+ cmd tests are updated to use the servertest package. Unfortunately it
wasn't yet possible to eliminate the concept of `remote=internal` in
favor of just using multiple sessions, because view initialization
involves calling both `go env` and `packages.Load`, which slow down
session startup significantly. See also golang.org/issue/35968.
Instead, the syntax for `-remote=internal` is modified to be
`-remote=internal@127.0.0.1:12345`.
+ An additional test for request cancellation is added for the
sessionserver package. This test uncovered a bug: when calling
Canceller.Cancel, we were using id rather than &id, which resulted in
incorrect json serialization (as only the pointer receiver implements
the json.Marshaller interface).
Updates golang/go#34111
Change-Id: I75c219df634348cdf53a9e57839b98588311a9ef
Reviewed-on: https://go-review.googlesource.com/c/tools/+/215742
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-01-21 17:34:50 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// ServeStream implements the jsonrpc2.StreamServer interface, by handling
|
|
|
|
// incoming streams using a new lsp server.
|
2020-05-07 14:32:20 -06:00
|
|
|
func (s *StreamServer) ServeStream(ctx context.Context, conn jsonrpc2.Conn) error {
|
internal/lsp: refactor LSP server instantiation
Previously, the process of instantiating and running the LSP server was
sharded across the lsp, protocol, and cmd packages, and this resulted in
some APIs that are hard to work with. For example, it's hard to guess
the difference between lsp.NewClientServer, lsp.NewServer,
protocol.NewServer (which returns a client), and protocol.NewClient
(which returns a server).
This change reorganizes Server instantiation as follows:
+ The lsp.Server is now purely an implementation of the protocol.Server
interface. It is no longer responsible for installing itself into the
jsonrpc2 Stream, nor for running itself.
+ A new package 'lsprpc' is added, to implement the logic of binding an
incoming connection to an LSP server session. This is put in a
separate package for lack of a clear home: it didn't really
philosophically belong in any of the lsp, cmd, or protocol packages.
We can perhaps move it to cmd in the future, but I'd like to keep it
as a separate package while I develop request forwarding.
simplified import graph:
jsonrpc2 ⭠ lsprpc ⭠ cmd
⭩ ⭦
lsp (t.b.d. client tests)
⭩ ⭨
protocol source
+ The jsonrpc2 package is extended to have a minimal API for running a
'StreamServer': something analogous to an HTTP server that listens
for new connections and delegates to a handler (but we couldn't use
the word 'Handler' for this delegate as it was already taken).
After these changes, I hope that the concerns of "serving the LSP",
"serving jsonrpc2", and "installing the LSP on jsonrpc2" are more
logically organized, though one legitimate criticism is that the word
'Server' is still heavily overloaded.
This change prepares a subsequent change which hijacks the jsonrpc2
connection when forwarding messages to a shared gopls instance.
To test this change, the following improvements are made:
+ A servertest package is added to make it easier to run a test against
an in-process jsonrpc2 server. For now, this uses TCP but it could
easily be modified to use io.Pipe.
+ cmd tests are updated to use the servertest package. Unfortunately it
wasn't yet possible to eliminate the concept of `remote=internal` in
favor of just using multiple sessions, because view initialization
involves calling both `go env` and `packages.Load`, which slow down
session startup significantly. See also golang.org/issue/35968.
Instead, the syntax for `-remote=internal` is modified to be
`-remote=internal@127.0.0.1:12345`.
+ An additional test for request cancellation is added for the
sessionserver package. This test uncovered a bug: when calling
Canceller.Cancel, we were using id rather than &id, which resulted in
incorrect json serialization (as only the pointer receiver implements
the json.Marshaller interface).
Updates golang/go#34111
Change-Id: I75c219df634348cdf53a9e57839b98588311a9ef
Reviewed-on: https://go-review.googlesource.com/c/tools/+/215742
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-01-21 17:34:50 -07:00
|
|
|
client := protocol.ClientDispatcher(conn)
|
2020-02-28 08:30:03 -07:00
|
|
|
session := s.cache.NewSession(ctx)
|
2020-02-19 08:17:48 -07:00
|
|
|
server := s.serverForTest
|
|
|
|
if server == nil {
|
2020-02-19 08:18:21 -07:00
|
|
|
server = lsp.NewServer(session, client)
|
2020-02-19 08:17:48 -07:00
|
|
|
}
|
2020-02-21 15:31:45 -07:00
|
|
|
// Clients may or may not send a shutdown message. Make sure the server is
|
|
|
|
// shut down.
|
|
|
|
// TODO(rFindley): this shutdown should perhaps be on a disconnected context.
|
2020-03-09 11:22:56 -06:00
|
|
|
defer func() {
|
|
|
|
if err := server.Shutdown(ctx); err != nil {
|
|
|
|
event.Error(ctx, "error shutting down", err)
|
|
|
|
}
|
|
|
|
}()
|
2020-02-18 10:47:19 -07:00
|
|
|
executable, err := os.Executable()
|
|
|
|
if err != nil {
|
2020-03-07 19:28:21 -07:00
|
|
|
log.Printf("error getting gopls path: %v", err)
|
2020-02-18 10:47:19 -07:00
|
|
|
executable = ""
|
|
|
|
}
|
2020-03-30 15:09:42 -06:00
|
|
|
ctx = protocol.WithClient(ctx, client)
|
2020-04-29 06:51:37 -06:00
|
|
|
conn.Go(ctx,
|
2020-04-02 10:43:08 -06:00
|
|
|
protocol.Handlers(
|
2020-08-19 13:48:36 -06:00
|
|
|
handshaker(session, executable, s.logConnections,
|
2020-04-02 10:43:08 -06:00
|
|
|
protocol.ServerHandler(server,
|
|
|
|
jsonrpc2.MethodNotFound))))
|
2020-07-08 11:49:07 -06:00
|
|
|
if s.logConnections {
|
|
|
|
log.Printf("Session %s: connected", session.ID())
|
|
|
|
defer log.Printf("Session %s: exited", session.ID())
|
|
|
|
}
|
2020-04-29 06:51:37 -06:00
|
|
|
<-conn.Done()
|
|
|
|
return conn.Err()
|
internal/lsp: refactor LSP server instantiation
Previously, the process of instantiating and running the LSP server was
sharded across the lsp, protocol, and cmd packages, and this resulted in
some APIs that are hard to work with. For example, it's hard to guess
the difference between lsp.NewClientServer, lsp.NewServer,
protocol.NewServer (which returns a client), and protocol.NewClient
(which returns a server).
This change reorganizes Server instantiation as follows:
+ The lsp.Server is now purely an implementation of the protocol.Server
interface. It is no longer responsible for installing itself into the
jsonrpc2 Stream, nor for running itself.
+ A new package 'lsprpc' is added, to implement the logic of binding an
incoming connection to an LSP server session. This is put in a
separate package for lack of a clear home: it didn't really
philosophically belong in any of the lsp, cmd, or protocol packages.
We can perhaps move it to cmd in the future, but I'd like to keep it
as a separate package while I develop request forwarding.
simplified import graph:
jsonrpc2 ⭠ lsprpc ⭠ cmd
⭩ ⭦
lsp (t.b.d. client tests)
⭩ ⭨
protocol source
+ The jsonrpc2 package is extended to have a minimal API for running a
'StreamServer': something analogous to an HTTP server that listens
for new connections and delegates to a handler (but we couldn't use
the word 'Handler' for this delegate as it was already taken).
After these changes, I hope that the concerns of "serving the LSP",
"serving jsonrpc2", and "installing the LSP on jsonrpc2" are more
logically organized, though one legitimate criticism is that the word
'Server' is still heavily overloaded.
This change prepares a subsequent change which hijacks the jsonrpc2
connection when forwarding messages to a shared gopls instance.
To test this change, the following improvements are made:
+ A servertest package is added to make it easier to run a test against
an in-process jsonrpc2 server. For now, this uses TCP but it could
easily be modified to use io.Pipe.
+ cmd tests are updated to use the servertest package. Unfortunately it
wasn't yet possible to eliminate the concept of `remote=internal` in
favor of just using multiple sessions, because view initialization
involves calling both `go env` and `packages.Load`, which slow down
session startup significantly. See also golang.org/issue/35968.
Instead, the syntax for `-remote=internal` is modified to be
`-remote=internal@127.0.0.1:12345`.
+ An additional test for request cancellation is added for the
sessionserver package. This test uncovered a bug: when calling
Canceller.Cancel, we were using id rather than &id, which resulted in
incorrect json serialization (as only the pointer receiver implements
the json.Marshaller interface).
Updates golang/go#34111
Change-Id: I75c219df634348cdf53a9e57839b98588311a9ef
Reviewed-on: https://go-review.googlesource.com/c/tools/+/215742
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2020-01-21 17:34:50 -07:00
|
|
|
}
|
2020-02-06 17:50:37 -07:00
|
|
|
|
|
|
|
// A Forwarder is a jsonrpc2.StreamServer that handles an LSP stream by
|
|
|
|
// forwarding it to a remote. This is used when the gopls process started by
|
|
|
|
// the editor is in the `-remote` mode, which means it finds and connects to a
|
|
|
|
// separate gopls daemon. In these cases, we still want the forwarder gopls to
|
|
|
|
// be instrumented with telemetry, and want to be able to in some cases hijack
|
|
|
|
// the jsonrpc2 connection with the daemon.
|
|
|
|
type Forwarder struct {
|
2020-02-10 09:34:13 -07:00
|
|
|
network, addr string
|
|
|
|
|
2020-03-09 11:22:56 -06:00
|
|
|
// goplsPath is the path to the current executing gopls binary.
|
|
|
|
goplsPath string
|
|
|
|
|
2020-08-12 11:52:56 -06:00
|
|
|
// configuration for the auto-started gopls remote.
|
|
|
|
remoteConfig remoteConfig
|
2020-03-09 11:22:56 -06:00
|
|
|
}
|
|
|
|
|
2020-08-12 11:52:56 -06:00
|
|
|
type remoteConfig struct {
|
|
|
|
debug string
|
|
|
|
listenTimeout time.Duration
|
|
|
|
logfile string
|
|
|
|
}
|
|
|
|
|
|
|
|
// A RemoteOption configures the behavior of the auto-started remote.
|
|
|
|
type RemoteOption interface {
|
|
|
|
set(*remoteConfig)
|
2020-03-09 11:22:56 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// RemoteDebugAddress configures the address used by the auto-started Gopls daemon
|
|
|
|
// for serving debug information.
|
|
|
|
type RemoteDebugAddress string
|
|
|
|
|
2020-08-12 11:52:56 -06:00
|
|
|
func (d RemoteDebugAddress) set(cfg *remoteConfig) {
|
|
|
|
cfg.debug = string(d)
|
2020-03-09 11:22:56 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// RemoteListenTimeout configures the amount of time the auto-started gopls
|
|
|
|
// daemon will wait with no client connections before shutting down.
|
|
|
|
type RemoteListenTimeout time.Duration
|
|
|
|
|
2020-08-12 11:52:56 -06:00
|
|
|
func (d RemoteListenTimeout) set(cfg *remoteConfig) {
|
|
|
|
cfg.listenTimeout = time.Duration(d)
|
2020-03-09 11:22:56 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// RemoteLogfile configures the logfile location for the auto-started gopls
|
|
|
|
// daemon.
|
|
|
|
type RemoteLogfile string
|
|
|
|
|
2020-08-12 11:52:56 -06:00
|
|
|
func (l RemoteLogfile) set(cfg *remoteConfig) {
|
|
|
|
cfg.logfile = string(l)
|
|
|
|
}
|
|
|
|
|
|
|
|
func defaultRemoteConfig() remoteConfig {
|
|
|
|
return remoteConfig{
|
|
|
|
listenTimeout: 1 * time.Minute,
|
|
|
|
}
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewForwarder creates a new Forwarder, ready to forward connections to the
|
2020-02-10 09:34:13 -07:00
|
|
|
// remote server specified by network and addr.
|
2020-08-12 11:52:56 -06:00
|
|
|
func NewForwarder(network, addr string, opts ...RemoteOption) *Forwarder {
|
2020-02-18 10:47:19 -07:00
|
|
|
gp, err := os.Executable()
|
|
|
|
if err != nil {
|
2020-03-07 19:28:21 -07:00
|
|
|
log.Printf("error getting gopls path for forwarder: %v", err)
|
2020-02-18 10:47:19 -07:00
|
|
|
gp = ""
|
|
|
|
}
|
2020-02-21 15:31:45 -07:00
|
|
|
|
2020-08-12 11:52:56 -06:00
|
|
|
rcfg := defaultRemoteConfig()
|
2020-03-09 11:22:56 -06:00
|
|
|
for _, opt := range opts {
|
2020-08-12 11:52:56 -06:00
|
|
|
opt.set(&rcfg)
|
|
|
|
}
|
|
|
|
|
|
|
|
fwd := &Forwarder{
|
|
|
|
network: network,
|
|
|
|
addr: addr,
|
|
|
|
goplsPath: gp,
|
|
|
|
remoteConfig: rcfg,
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
2020-03-09 11:22:56 -06:00
|
|
|
return fwd
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
|
|
|
|
2020-03-06 08:02:36 -07:00
|
|
|
// QueryServerState queries the server state of the current server.
|
|
|
|
func QueryServerState(ctx context.Context, network, address string) (*ServerState, error) {
|
|
|
|
if network == AutoNetwork {
|
|
|
|
gp, err := os.Executable()
|
|
|
|
if err != nil {
|
2020-05-01 08:05:39 -06:00
|
|
|
return nil, fmt.Errorf("getting gopls path: %w", err)
|
2020-03-06 08:02:36 -07:00
|
|
|
}
|
|
|
|
network, address = autoNetworkAddress(gp, address)
|
|
|
|
}
|
|
|
|
netConn, err := net.DialTimeout(network, address, 5*time.Second)
|
|
|
|
if err != nil {
|
2020-05-01 08:05:39 -06:00
|
|
|
return nil, fmt.Errorf("dialing remote: %w", err)
|
2020-03-06 08:02:36 -07:00
|
|
|
}
|
2020-04-27 13:35:20 -06:00
|
|
|
serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn))
|
2020-04-29 06:51:37 -06:00
|
|
|
serverConn.Go(ctx, jsonrpc2.MethodNotFound)
|
2020-03-06 08:02:36 -07:00
|
|
|
var state ServerState
|
2020-04-07 19:35:47 -06:00
|
|
|
if err := protocol.Call(ctx, serverConn, sessionsMethod, nil, &state); err != nil {
|
2020-05-01 08:05:39 -06:00
|
|
|
return nil, fmt.Errorf("querying server state: %w", err)
|
2020-03-06 08:02:36 -07:00
|
|
|
}
|
|
|
|
return &state, nil
|
|
|
|
}
|
|
|
|
|
2020-02-06 17:50:37 -07:00
|
|
|
// ServeStream dials the forwarder remote and binds the remote to serve the LSP
|
|
|
|
// on the incoming stream.
|
2020-05-07 14:32:20 -06:00
|
|
|
func (f *Forwarder) ServeStream(ctx context.Context, clientConn jsonrpc2.Conn) error {
|
2020-02-06 17:50:37 -07:00
|
|
|
client := protocol.ClientDispatcher(clientConn)
|
|
|
|
|
2020-02-18 10:47:19 -07:00
|
|
|
netConn, err := f.connectToRemote(ctx)
|
2020-02-06 17:50:37 -07:00
|
|
|
if err != nil {
|
2020-05-01 08:05:39 -06:00
|
|
|
return fmt.Errorf("forwarder: connecting to remote: %w", err)
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
2020-04-27 13:35:20 -06:00
|
|
|
serverConn := jsonrpc2.NewConn(jsonrpc2.NewHeaderStream(netConn))
|
2020-02-06 17:50:37 -07:00
|
|
|
server := protocol.ServerDispatcher(serverConn)
|
|
|
|
|
|
|
|
// Forward between connections.
|
2020-04-29 06:51:37 -06:00
|
|
|
serverConn.Go(ctx,
|
|
|
|
protocol.Handlers(
|
|
|
|
protocol.ClientHandler(client,
|
|
|
|
jsonrpc2.MethodNotFound)))
|
2020-02-25 10:01:11 -07:00
|
|
|
// Don't run the clientConn yet, so that we can complete the handshake before
|
|
|
|
// processing any client messages.
|
2020-02-19 08:18:21 -07:00
|
|
|
|
|
|
|
// Do a handshake with the server instance to exchange debug information.
|
|
|
|
index := atomic.AddInt64(&serverIndex, 1)
|
|
|
|
serverID := strconv.FormatInt(index, 10)
|
|
|
|
var (
|
|
|
|
hreq = handshakeRequest{
|
|
|
|
ServerID: serverID,
|
2020-02-18 10:47:19 -07:00
|
|
|
GoplsPath: f.goplsPath,
|
2020-02-19 08:18:21 -07:00
|
|
|
}
|
|
|
|
hresp handshakeResponse
|
|
|
|
)
|
2020-06-03 15:06:45 -06:00
|
|
|
if di := debug.GetInstance(ctx); di != nil {
|
2020-02-28 08:30:03 -07:00
|
|
|
hreq.Logfile = di.Logfile
|
|
|
|
hreq.DebugAddr = di.ListenedDebugAddress
|
|
|
|
}
|
2020-04-07 19:35:47 -06:00
|
|
|
if err := protocol.Call(ctx, serverConn, handshakeMethod, hreq, &hresp); err != nil {
|
2020-03-07 19:28:21 -07:00
|
|
|
event.Error(ctx, "forwarder: gopls handshake failed", err)
|
2020-02-18 10:47:19 -07:00
|
|
|
}
|
|
|
|
if hresp.GoplsPath != f.goplsPath {
|
2020-03-07 19:28:21 -07:00
|
|
|
event.Error(ctx, "", fmt.Errorf("forwarder: gopls path mismatch: forwarder is %q, remote is %q", f.goplsPath, hresp.GoplsPath))
|
2020-02-19 08:18:21 -07:00
|
|
|
}
|
2020-06-03 15:06:45 -06:00
|
|
|
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),
|
|
|
|
)
|
2020-04-29 06:51:37 -06:00
|
|
|
clientConn.Go(ctx,
|
|
|
|
protocol.Handlers(
|
2020-05-14 21:15:12 -06:00
|
|
|
forwarderHandler(
|
|
|
|
protocol.ServerHandler(server,
|
|
|
|
jsonrpc2.MethodNotFound))))
|
2020-04-30 13:59:37 -06:00
|
|
|
|
|
|
|
select {
|
|
|
|
case <-serverConn.Done():
|
|
|
|
clientConn.Close()
|
|
|
|
case <-clientConn.Done():
|
|
|
|
serverConn.Close()
|
|
|
|
}
|
2020-04-29 06:51:37 -06:00
|
|
|
|
|
|
|
err = serverConn.Err()
|
|
|
|
if err == nil {
|
|
|
|
err = clientConn.Err()
|
|
|
|
}
|
|
|
|
return err
|
2020-02-06 17:50:37 -07:00
|
|
|
}
|
2020-02-09 12:44:03 -07:00
|
|
|
|
2020-02-18 10:47:19 -07:00
|
|
|
func (f *Forwarder) connectToRemote(ctx context.Context) (net.Conn, error) {
|
2020-08-12 11:52:56 -06:00
|
|
|
return connectToRemote(ctx, f.network, f.addr, f.goplsPath, f.remoteConfig)
|
|
|
|
}
|
|
|
|
|
|
|
|
func ConnectToRemote(ctx context.Context, network, addr string, opts ...RemoteOption) (net.Conn, error) {
|
|
|
|
rcfg := defaultRemoteConfig()
|
|
|
|
for _, opt := range opts {
|
|
|
|
opt.set(&rcfg)
|
|
|
|
}
|
|
|
|
// This is not strictly necessary, as it won't be used if not connecting to
|
|
|
|
// the 'auto' remote.
|
|
|
|
goplsPath, err := os.Executable()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to resolve gopls path: %v", err)
|
|
|
|
}
|
|
|
|
return connectToRemote(ctx, network, addr, goplsPath, rcfg)
|
|
|
|
}
|
|
|
|
|
|
|
|
func connectToRemote(ctx context.Context, inNetwork, inAddr, goplsPath string, rcfg remoteConfig) (net.Conn, error) {
|
2020-02-18 10:47:19 -07:00
|
|
|
var (
|
|
|
|
netConn net.Conn
|
|
|
|
err error
|
2020-08-12 11:52:56 -06:00
|
|
|
network, address = inNetwork, inAddr
|
2020-02-18 10:47:19 -07:00
|
|
|
)
|
2020-08-12 11:52:56 -06:00
|
|
|
if inNetwork == AutoNetwork {
|
2020-02-18 10:47:19 -07:00
|
|
|
// f.network is overloaded to support a concept of 'automatic' addresses,
|
|
|
|
// which signals that the gopls remote address should be automatically
|
|
|
|
// derived.
|
|
|
|
// So we need to resolve a real network and address here.
|
2020-08-12 11:52:56 -06:00
|
|
|
network, address = autoNetworkAddress(goplsPath, inAddr)
|
2020-02-18 10:47:19 -07:00
|
|
|
}
|
2020-03-05 15:54:57 -07:00
|
|
|
// Attempt to verify that we own the remote. This is imperfect, but if we can
|
|
|
|
// determine that the remote is owned by a different user, we should fail.
|
|
|
|
ok, err := verifyRemoteOwnership(network, address)
|
|
|
|
if err != nil {
|
|
|
|
// If the ownership check itself failed, we fail open but log an error to
|
|
|
|
// the user.
|
2020-05-04 13:12:06 -06:00
|
|
|
event.Error(ctx, "unable to check daemon socket owner, failing open", err)
|
2020-03-05 15:54:57 -07:00
|
|
|
} else if !ok {
|
|
|
|
// We succesfully checked that the socket is not owned by us, we fail
|
|
|
|
// closed.
|
|
|
|
return nil, fmt.Errorf("socket %q is owned by a different user", address)
|
|
|
|
}
|
2020-08-12 11:52:56 -06:00
|
|
|
const dialTimeout = 1 * time.Second
|
2020-02-18 10:47:19 -07:00
|
|
|
// Try dialing our remote once, in case it is already running.
|
2020-08-12 11:52:56 -06:00
|
|
|
netConn, err = net.DialTimeout(network, address, dialTimeout)
|
2020-02-18 10:47:19 -07:00
|
|
|
if err == nil {
|
|
|
|
return netConn, nil
|
|
|
|
}
|
|
|
|
// If our remote is on the 'auto' network, start it if it doesn't exist.
|
2020-08-12 11:52:56 -06:00
|
|
|
if inNetwork == AutoNetwork {
|
|
|
|
if goplsPath == "" {
|
2020-02-18 10:47:19 -07:00
|
|
|
return nil, fmt.Errorf("cannot auto-start remote: gopls path is unknown")
|
|
|
|
}
|
|
|
|
if network == "unix" {
|
|
|
|
// Sometimes the socketfile isn't properly cleaned up when gopls shuts
|
|
|
|
// down. Since we have already tried and failed to dial this address, it
|
|
|
|
// should *usually* be safe to remove the socket before binding to the
|
|
|
|
// address.
|
|
|
|
// TODO(rfindley): there is probably a race here if multiple gopls
|
|
|
|
// instances are simultaneously starting up.
|
|
|
|
if _, err := os.Stat(address); err == nil {
|
|
|
|
if err := os.Remove(address); err != nil {
|
2020-05-01 08:05:39 -06:00
|
|
|
return nil, fmt.Errorf("removing remote socket file: %w", err)
|
2020-02-18 10:47:19 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-02-26 14:22:54 -07:00
|
|
|
args := []string{"serve",
|
|
|
|
"-listen", fmt.Sprintf(`%s;%s`, network, address),
|
2020-08-12 11:52:56 -06:00
|
|
|
"-listen.timeout", rcfg.listenTimeout.String(),
|
2020-07-08 11:49:07 -06:00
|
|
|
}
|
2020-08-12 11:52:56 -06:00
|
|
|
if rcfg.logfile != "" {
|
|
|
|
args = append(args, "-logfile", rcfg.logfile)
|
2020-03-09 11:22:56 -06:00
|
|
|
}
|
2020-08-12 11:52:56 -06:00
|
|
|
if rcfg.debug != "" {
|
|
|
|
args = append(args, "-debug", rcfg.debug)
|
2020-02-26 14:22:54 -07:00
|
|
|
}
|
2020-08-12 11:52:56 -06:00
|
|
|
if err := startRemote(goplsPath, args...); err != nil {
|
|
|
|
return nil, fmt.Errorf("startRemote(%q, %v): %w", goplsPath, args, err)
|
2020-02-18 10:47:19 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-12 11:52:56 -06:00
|
|
|
const retries = 5
|
2020-02-18 10:47:19 -07:00
|
|
|
// It can take some time for the newly started server to bind to our address,
|
|
|
|
// so we retry for a bit.
|
2020-08-12 11:52:56 -06:00
|
|
|
for retry := 0; retry < retries; retry++ {
|
2020-02-18 10:47:19 -07:00
|
|
|
startDial := time.Now()
|
2020-08-12 11:52:56 -06:00
|
|
|
netConn, err = net.DialTimeout(network, address, dialTimeout)
|
2020-02-18 10:47:19 -07:00
|
|
|
if err == nil {
|
|
|
|
return netConn, nil
|
|
|
|
}
|
2020-04-20 10:14:12 -06:00
|
|
|
event.Log(ctx, fmt.Sprintf("failed attempt #%d to connect to remote: %v\n", retry+2, err))
|
2020-02-18 10:47:19 -07:00
|
|
|
// In case our failure was a fast-failure, ensure we wait at least
|
|
|
|
// f.dialTimeout before trying again.
|
2020-08-12 11:52:56 -06:00
|
|
|
if retry != retries-1 {
|
|
|
|
time.Sleep(dialTimeout - time.Since(startDial))
|
2020-02-18 10:47:19 -07:00
|
|
|
}
|
|
|
|
}
|
2020-05-01 08:05:39 -06:00
|
|
|
return nil, fmt.Errorf("dialing remote: %w", err)
|
2020-02-18 10:47:19 -07:00
|
|
|
}
|
|
|
|
|
2020-05-14 21:15:12 -06:00
|
|
|
// forwarderHandler intercepts 'exit' messages to prevent the shared gopls
|
|
|
|
// instance from exiting. In the future it may also intercept 'shutdown' to
|
|
|
|
// provide more graceful shutdown of the client connection.
|
|
|
|
func forwarderHandler(handler jsonrpc2.Handler) jsonrpc2.Handler {
|
|
|
|
return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error {
|
|
|
|
// The gopls workspace environment defaults to the process environment in
|
|
|
|
// which gopls daemon was started. To avoid discrepancies in Go environment
|
|
|
|
// between the editor and daemon, inject any unset variables in `go env`
|
|
|
|
// into the options sent by initialize.
|
|
|
|
//
|
|
|
|
// See also golang.org/issue/37830.
|
|
|
|
if r.Method() == "initialize" {
|
|
|
|
if newr, err := addGoEnvToInitializeRequest(ctx, r); err == nil {
|
|
|
|
r = newr
|
|
|
|
} else {
|
|
|
|
log.Printf("unable to add local env to initialize request: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return handler(ctx, reply, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// addGoEnvToInitializeRequest builds a new initialize request in which we set
|
|
|
|
// any environment variables output by `go env` and not already present in the
|
|
|
|
// request.
|
|
|
|
//
|
|
|
|
// It returns an error if r is not an initialize requst, or is otherwise
|
|
|
|
// malformed.
|
|
|
|
func addGoEnvToInitializeRequest(ctx context.Context, r jsonrpc2.Request) (jsonrpc2.Request, error) {
|
|
|
|
var params protocol.ParamInitialize
|
|
|
|
if err := json.Unmarshal(r.Params(), ¶ms); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var opts map[string]interface{}
|
|
|
|
switch v := params.InitializationOptions.(type) {
|
|
|
|
case nil:
|
|
|
|
opts = make(map[string]interface{})
|
|
|
|
case map[string]interface{}:
|
|
|
|
opts = v
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unexpected type for InitializationOptions: %T", v)
|
|
|
|
}
|
|
|
|
envOpt, ok := opts["env"]
|
|
|
|
if !ok {
|
|
|
|
envOpt = make(map[string]interface{})
|
|
|
|
}
|
|
|
|
env, ok := envOpt.(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf(`env option is %T, expected a map`, envOpt)
|
|
|
|
}
|
|
|
|
goenv, err := getGoEnv(ctx, env)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for govar, value := range goenv {
|
|
|
|
env[govar] = value
|
|
|
|
}
|
|
|
|
opts["env"] = env
|
|
|
|
params.InitializationOptions = opts
|
|
|
|
call, ok := r.(*jsonrpc2.Call)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("%T is not a *jsonrpc2.Call", r)
|
|
|
|
}
|
|
|
|
return jsonrpc2.NewCall(call.ID(), "initialize", params)
|
|
|
|
}
|
|
|
|
|
|
|
|
func getGoEnv(ctx context.Context, env map[string]interface{}) (map[string]string, error) {
|
|
|
|
var runEnv []string
|
|
|
|
for k, v := range env {
|
|
|
|
runEnv = append(runEnv, fmt.Sprintf("%s=%s", k, v))
|
|
|
|
}
|
|
|
|
runner := gocommand.Runner{}
|
|
|
|
output, err := runner.Run(ctx, gocommand.Invocation{
|
|
|
|
Verb: "env",
|
|
|
|
Args: []string{"-json"},
|
|
|
|
Env: runEnv,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
envmap := make(map[string]string)
|
|
|
|
if err := json.Unmarshal(output.Bytes(), &envmap); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return envmap, nil
|
|
|
|
}
|
|
|
|
|
2020-03-06 08:02:36 -07:00
|
|
|
// A handshakeRequest identifies a client to the LSP server.
|
2020-02-19 08:18:21 -07:00
|
|
|
type handshakeRequest struct {
|
2020-03-06 08:02:36 -07:00
|
|
|
// ServerID is the ID of the server on the client. This should usually be 0.
|
|
|
|
ServerID string `json:"serverID"`
|
|
|
|
// Logfile is the location of the clients log file.
|
|
|
|
Logfile string `json:"logfile"`
|
|
|
|
// DebugAddr is the client debug address.
|
2020-02-19 08:18:21 -07:00
|
|
|
DebugAddr string `json:"debugAddr"`
|
2020-03-06 08:02:36 -07:00
|
|
|
// GoplsPath is the path to the Gopls binary running the current client
|
|
|
|
// process.
|
2020-02-18 10:47:19 -07:00
|
|
|
GoplsPath string `json:"goplsPath"`
|
2020-02-19 08:18:21 -07:00
|
|
|
}
|
|
|
|
|
2020-03-06 08:02:36 -07:00
|
|
|
// A handshakeResponse is returned by the LSP server to tell the LSP client
|
|
|
|
// information about its session.
|
2020-02-19 08:18:21 -07:00
|
|
|
type handshakeResponse struct {
|
2020-03-06 08:02:36 -07:00
|
|
|
// SessionID is the server session associated with the client.
|
|
|
|
SessionID string `json:"sessionID"`
|
|
|
|
// Logfile is the location of the server logs.
|
|
|
|
Logfile string `json:"logfile"`
|
|
|
|
// DebugAddr is the server debug address.
|
|
|
|
DebugAddr string `json:"debugAddr"`
|
|
|
|
// GoplsPath is the path to the Gopls binary running the current server
|
|
|
|
// process.
|
|
|
|
GoplsPath string `json:"goplsPath"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ClientSession identifies a current client LSP session on the server. Note
|
|
|
|
// that it looks similar to handshakeResposne, but in fact 'Logfile' and
|
|
|
|
// 'DebugAddr' now refer to the client.
|
|
|
|
type ClientSession struct {
|
2020-02-19 08:18:21 -07:00
|
|
|
SessionID string `json:"sessionID"`
|
|
|
|
Logfile string `json:"logfile"`
|
|
|
|
DebugAddr string `json:"debugAddr"`
|
|
|
|
}
|
|
|
|
|
2020-03-06 08:02:36 -07:00
|
|
|
// ServerState holds information about the gopls daemon process, including its
|
|
|
|
// debug information and debug information of all of its current connected
|
|
|
|
// clients.
|
|
|
|
type ServerState struct {
|
|
|
|
Logfile string `json:"logfile"`
|
|
|
|
DebugAddr string `json:"debugAddr"`
|
|
|
|
GoplsPath string `json:"goplsPath"`
|
|
|
|
CurrentClientID string `json:"currentClientID"`
|
|
|
|
Clients []ClientSession `json:"clients"`
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
handshakeMethod = "gopls/handshake"
|
|
|
|
sessionsMethod = "gopls/sessions"
|
|
|
|
)
|
2020-02-19 08:18:21 -07:00
|
|
|
|
2020-08-19 13:48:36 -06:00
|
|
|
func handshaker(session *cache.Session, goplsPath string, logHandshakes bool, handler jsonrpc2.Handler) jsonrpc2.Handler {
|
2020-04-12 20:47:10 -06:00
|
|
|
return func(ctx context.Context, reply jsonrpc2.Replier, r jsonrpc2.Request) error {
|
|
|
|
switch r.Method() {
|
2020-03-30 15:09:42 -06:00
|
|
|
case handshakeMethod:
|
2020-07-08 11:49:07 -06:00
|
|
|
// We log.Printf in this handler, rather than event.Log when we want logs
|
|
|
|
// to go to the daemon log rather than being reflected back to the
|
|
|
|
// client.
|
2020-03-30 15:09:42 -06:00
|
|
|
var req handshakeRequest
|
2020-04-12 20:47:10 -06:00
|
|
|
if err := json.Unmarshal(r.Params(), &req); err != nil {
|
2020-08-19 13:48:36 -06:00
|
|
|
if logHandshakes {
|
|
|
|
log.Printf("Error processing handshake for session %s: %v", session.ID(), err)
|
|
|
|
}
|
2020-04-12 20:47:10 -06:00
|
|
|
sendError(ctx, reply, err)
|
2020-03-30 15:09:42 -06:00
|
|
|
return nil
|
|
|
|
}
|
2020-08-19 13:48:36 -06:00
|
|
|
if logHandshakes {
|
|
|
|
log.Printf("Session %s: got handshake. Logfile: %q, Debug addr: %q", session.ID(), req.Logfile, req.DebugAddr)
|
|
|
|
}
|
2020-06-03 15:06:45 -06:00
|
|
|
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),
|
|
|
|
)
|
2020-03-30 15:09:42 -06:00
|
|
|
resp := handshakeResponse{
|
2020-06-03 15:06:45 -06:00
|
|
|
SessionID: session.ID(),
|
2020-03-30 15:09:42 -06:00
|
|
|
GoplsPath: goplsPath,
|
|
|
|
}
|
|
|
|
if di := debug.GetInstance(ctx); di != nil {
|
|
|
|
resp.Logfile = di.Logfile
|
|
|
|
resp.DebugAddr = di.ListenedDebugAddress
|
|
|
|
}
|
2020-02-28 08:30:03 -07:00
|
|
|
|
2020-04-09 21:54:23 -06:00
|
|
|
return reply(ctx, resp, nil)
|
2020-03-30 15:09:42 -06:00
|
|
|
case sessionsMethod:
|
|
|
|
resp := ServerState{
|
|
|
|
GoplsPath: goplsPath,
|
2020-06-03 15:06:45 -06:00
|
|
|
CurrentClientID: session.ID(),
|
2020-03-30 15:09:42 -06:00
|
|
|
}
|
|
|
|
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{
|
2020-06-02 19:50:41 -06:00
|
|
|
SessionID: c.Session.ID(),
|
|
|
|
Logfile: c.Logfile,
|
|
|
|
DebugAddr: c.DebugAddress,
|
2020-03-30 15:09:42 -06:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2020-04-09 21:54:23 -06:00
|
|
|
return reply(ctx, resp, nil)
|
2020-03-06 08:02:36 -07:00
|
|
|
}
|
2020-04-09 21:54:23 -06:00
|
|
|
return handler(ctx, reply, r)
|
2020-02-19 08:18:21 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-12 20:47:10 -06:00
|
|
|
func sendError(ctx context.Context, reply jsonrpc2.Replier, err error) {
|
2020-04-09 21:00:00 -06:00
|
|
|
err = fmt.Errorf("%w: %v", jsonrpc2.ErrParse, err)
|
2020-04-09 21:54:23 -06:00
|
|
|
if err := reply(ctx, nil, err); err != nil {
|
2020-03-07 19:28:21 -07:00
|
|
|
event.Error(ctx, "", err)
|
2020-02-19 08:18:21 -07:00
|
|
|
}
|
|
|
|
}
|