Replaced net/rpc with custom version
This version preserves the order of requests, allows the client to switch between API versions and introduces a way to send notifications to the client (see TODO item at: proc/proc_linux.go:325). Fixes #523, #571
This commit is contained in:
parent
51c39ed171
commit
80336e57e0
@ -15,8 +15,8 @@ import (
|
|||||||
"github.com/derekparker/delve/config"
|
"github.com/derekparker/delve/config"
|
||||||
"github.com/derekparker/delve/service"
|
"github.com/derekparker/delve/service"
|
||||||
"github.com/derekparker/delve/service/api"
|
"github.com/derekparker/delve/service/api"
|
||||||
"github.com/derekparker/delve/service/rpc1"
|
|
||||||
"github.com/derekparker/delve/service/rpc2"
|
"github.com/derekparker/delve/service/rpc2"
|
||||||
|
"github.com/derekparker/delve/service/rpccommon"
|
||||||
"github.com/derekparker/delve/terminal"
|
"github.com/derekparker/delve/terminal"
|
||||||
"github.com/derekparker/delve/version"
|
"github.com/derekparker/delve/version"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -27,8 +27,8 @@ var (
|
|||||||
Log bool
|
Log bool
|
||||||
// Headless is whether to run without terminal.
|
// Headless is whether to run without terminal.
|
||||||
Headless bool
|
Headless bool
|
||||||
// ApiVersion is the requested API version while running headless
|
// APIVersion is the requested API version while running headless
|
||||||
ApiVersion int
|
APIVersion int
|
||||||
// AcceptMulti allows multiple clients to connect to the same server
|
// AcceptMulti allows multiple clients to connect to the same server
|
||||||
AcceptMulti bool
|
AcceptMulti bool
|
||||||
// Addr is the debugging server listen address.
|
// Addr is the debugging server listen address.
|
||||||
@ -81,7 +81,7 @@ func New() *cobra.Command {
|
|||||||
RootCommand.PersistentFlags().BoolVarP(&Log, "log", "", false, "Enable debugging server logging.")
|
RootCommand.PersistentFlags().BoolVarP(&Log, "log", "", false, "Enable debugging server logging.")
|
||||||
RootCommand.PersistentFlags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.")
|
RootCommand.PersistentFlags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.")
|
||||||
RootCommand.PersistentFlags().BoolVarP(&AcceptMulti, "accept-multiclient", "", false, "Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.")
|
RootCommand.PersistentFlags().BoolVarP(&AcceptMulti, "accept-multiclient", "", false, "Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.")
|
||||||
RootCommand.PersistentFlags().IntVar(&ApiVersion, "api-version", 1, "Selects API version when headless.")
|
RootCommand.PersistentFlags().IntVar(&APIVersion, "api-version", 1, "Selects API version when headless.")
|
||||||
RootCommand.PersistentFlags().StringVar(&InitFile, "init", "", "Init file, executed by the terminal client.")
|
RootCommand.PersistentFlags().StringVar(&InitFile, "init", "", "Init file, executed by the terminal client.")
|
||||||
RootCommand.PersistentFlags().StringVar(&BuildFlags, "build-flags", buildFlagsDefault, "Build flags, to be passed to the compiler.")
|
RootCommand.PersistentFlags().StringVar(&BuildFlags, "build-flags", buildFlagsDefault, "Build flags, to be passed to the compiler.")
|
||||||
|
|
||||||
@ -268,10 +268,11 @@ func traceCmd(cmd *cobra.Command, args []string) {
|
|||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
|
||||||
// Create and start a debug server
|
// Create and start a debug server
|
||||||
server := rpc2.NewServer(&service.Config{
|
server := rpccommon.NewServer(&service.Config{
|
||||||
Listener: listener,
|
Listener: listener,
|
||||||
ProcessArgs: processArgs,
|
ProcessArgs: processArgs,
|
||||||
AttachPid: traceAttachPid,
|
AttachPid: traceAttachPid,
|
||||||
|
APIVersion: 2,
|
||||||
}, Log)
|
}, Log)
|
||||||
if err := server.Run(); err != nil {
|
if err := server.Run(); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
@ -386,28 +387,18 @@ func execute(attachPid int, processArgs []string, conf *config.Config, kind exec
|
|||||||
Stop(bool) error
|
Stop(bool) error
|
||||||
}
|
}
|
||||||
|
|
||||||
if !Headless {
|
|
||||||
ApiVersion = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and start a debugger server
|
// Create and start a debugger server
|
||||||
switch ApiVersion {
|
switch APIVersion {
|
||||||
case 1:
|
case 1, 2:
|
||||||
server = rpc1.NewServer(&service.Config{
|
server = rpccommon.NewServer(&service.Config{
|
||||||
Listener: listener,
|
|
||||||
ProcessArgs: processArgs,
|
|
||||||
AttachPid: attachPid,
|
|
||||||
AcceptMulti: AcceptMulti,
|
|
||||||
}, Log)
|
|
||||||
case 2:
|
|
||||||
server = rpc2.NewServer(&service.Config{
|
|
||||||
Listener: listener,
|
Listener: listener,
|
||||||
ProcessArgs: processArgs,
|
ProcessArgs: processArgs,
|
||||||
AttachPid: attachPid,
|
AttachPid: attachPid,
|
||||||
AcceptMulti: AcceptMulti,
|
AcceptMulti: AcceptMulti,
|
||||||
|
APIVersion: APIVersion,
|
||||||
}, Log)
|
}, Log)
|
||||||
default:
|
default:
|
||||||
fmt.Println("Unknown API version %d", ApiVersion)
|
fmt.Println("Unknown API version %d", APIVersion)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -273,3 +273,18 @@ type AsmInstruction struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AsmInstructions []AsmInstruction
|
type AsmInstructions []AsmInstruction
|
||||||
|
|
||||||
|
type GetVersionIn struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetVersionOut struct {
|
||||||
|
DelveVersion string
|
||||||
|
APIVersion int
|
||||||
|
}
|
||||||
|
|
||||||
|
type SetAPIVersionIn struct {
|
||||||
|
APIVersion int
|
||||||
|
}
|
||||||
|
|
||||||
|
type SetAPIVersionOut struct {
|
||||||
|
}
|
||||||
|
|||||||
@ -16,7 +16,9 @@ type Config struct {
|
|||||||
// AttachPid is the PID of an existing process to which the debugger should
|
// AttachPid is the PID of an existing process to which the debugger should
|
||||||
// attach.
|
// attach.
|
||||||
AttachPid int
|
AttachPid int
|
||||||
// AcceptMulti configures the server to accept multiple connection
|
// AcceptMulti configures the server to accept multiple connection.
|
||||||
// Note that the server API is not reentrant and clients will have to coordinate
|
// Note that the server API is not reentrant and clients will have to coordinate.
|
||||||
AcceptMulti bool
|
AcceptMulti bool
|
||||||
|
// APIVersion selects which version of the API to serve (default: 1).
|
||||||
|
APIVersion int
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,6 @@ package rpc1
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
grpc "net/rpc"
|
|
||||||
"net/rpc/jsonrpc"
|
|
||||||
|
|
||||||
"github.com/derekparker/delve/proc"
|
"github.com/derekparker/delve/proc"
|
||||||
"github.com/derekparker/delve/service"
|
"github.com/derekparker/delve/service"
|
||||||
@ -17,87 +12,15 @@ import (
|
|||||||
|
|
||||||
var defaultLoadConfig = proc.LoadConfig{true, 1, 64, 64, -1}
|
var defaultLoadConfig = proc.LoadConfig{true, 1, 64, 64, -1}
|
||||||
|
|
||||||
type ServerImpl struct {
|
|
||||||
s *RPCServer
|
|
||||||
}
|
|
||||||
|
|
||||||
type RPCServer struct {
|
type RPCServer struct {
|
||||||
// config is all the information necessary to start the debugger and server.
|
// config is all the information necessary to start the debugger and server.
|
||||||
config *service.Config
|
config *service.Config
|
||||||
// listener is used to serve HTTP.
|
|
||||||
listener net.Listener
|
|
||||||
// stopChan is used to stop the listener goroutine
|
|
||||||
stopChan chan struct{}
|
|
||||||
// debugger is a debugger service.
|
// debugger is a debugger service.
|
||||||
debugger *debugger.Debugger
|
debugger *debugger.Debugger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer creates a new RPCServer.
|
func NewServer(config *service.Config, debugger *debugger.Debugger) *RPCServer {
|
||||||
func NewServer(config *service.Config, logEnabled bool) *ServerImpl {
|
return &RPCServer{config, debugger}
|
||||||
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
|
||||||
if !logEnabled {
|
|
||||||
log.SetOutput(ioutil.Discard)
|
|
||||||
}
|
|
||||||
log.Printf("Using API v1")
|
|
||||||
|
|
||||||
return &ServerImpl{
|
|
||||||
&RPCServer{
|
|
||||||
config: config,
|
|
||||||
listener: config.Listener,
|
|
||||||
stopChan: make(chan struct{}),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop detaches from the debugger and waits for it to stop.
|
|
||||||
func (s *ServerImpl) Stop(kill bool) error {
|
|
||||||
if s.s.config.AcceptMulti {
|
|
||||||
close(s.s.stopChan)
|
|
||||||
s.s.listener.Close()
|
|
||||||
}
|
|
||||||
err := s.s.debugger.Detach(kill)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run starts a debugger and exposes it with an HTTP server. The debugger
|
|
||||||
// itself can be stopped with the `detach` API. Run blocks until the HTTP
|
|
||||||
// server stops.
|
|
||||||
func (s *ServerImpl) Run() error {
|
|
||||||
var err error
|
|
||||||
// Create and start the debugger
|
|
||||||
if s.s.debugger, err = debugger.New(&debugger.Config{
|
|
||||||
ProcessArgs: s.s.config.ProcessArgs,
|
|
||||||
AttachPid: s.s.config.AttachPid,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rpcs := grpc.NewServer()
|
|
||||||
rpcs.Register(s.s)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer s.s.listener.Close()
|
|
||||||
for {
|
|
||||||
c, err := s.s.listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
select {
|
|
||||||
case <-s.s.stopChan:
|
|
||||||
// We were supposed to exit, do nothing and return
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
go rpcs.ServeCodec(jsonrpc.NewServerCodec(c))
|
|
||||||
if !s.s.config.AcceptMulti {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RPCServer) ProcessPid(arg1 interface{}, pid *int) error {
|
func (s *RPCServer) ProcessPid(arg1 interface{}, pid *int) error {
|
||||||
@ -109,10 +32,6 @@ func (s *RPCServer) Detach(kill bool, ret *int) error {
|
|||||||
return s.debugger.Detach(kill)
|
return s.debugger.Detach(kill)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerImpl) Restart() error {
|
|
||||||
return s.s.Restart(nil, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *RPCServer) Restart(arg1 interface{}, arg2 *int) error {
|
func (s *RPCServer) Restart(arg1 interface{}, arg2 *int) error {
|
||||||
if s.config.AttachPid != 0 {
|
if s.config.AttachPid != 0 {
|
||||||
return errors.New("cannot restart process Delve did not create")
|
return errors.New("cannot restart process Delve did not create")
|
||||||
@ -129,13 +48,9 @@ func (s *RPCServer) State(arg interface{}, state *api.DebuggerState) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RPCServer) Command(command *api.DebuggerCommand, state *api.DebuggerState) error {
|
func (s *RPCServer) Command(command *api.DebuggerCommand, cb service.RPCCallback) {
|
||||||
st, err := s.debugger.Command(command)
|
st, err := s.debugger.Command(command)
|
||||||
if err != nil {
|
cb.Return(st, err)
|
||||||
return err
|
|
||||||
}
|
|
||||||
*state = *st
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RPCServer) GetBreakpoint(id int, breakpoint *api.Breakpoint) error {
|
func (s *RPCServer) GetBreakpoint(id int, breakpoint *api.Breakpoint) error {
|
||||||
|
|||||||
@ -26,10 +26,9 @@ func NewClient(addr string) *RPCClient {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("dialing:", err)
|
log.Fatal("dialing:", err)
|
||||||
}
|
}
|
||||||
return &RPCClient{
|
c := &RPCClient{addr: addr, client: client}
|
||||||
addr: addr,
|
c.call("SetApiVersion", api.SetAPIVersionIn{2}, &api.SetAPIVersionOut{})
|
||||||
client: client,
|
return c
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RPCClient) ProcessPid() int {
|
func (c *RPCClient) ProcessPid() int {
|
||||||
|
|||||||
@ -3,101 +3,21 @@ package rpc2
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
grpc "net/rpc"
|
|
||||||
"net/rpc/jsonrpc"
|
|
||||||
|
|
||||||
"github.com/derekparker/delve/service"
|
"github.com/derekparker/delve/service"
|
||||||
"github.com/derekparker/delve/service/api"
|
"github.com/derekparker/delve/service/api"
|
||||||
"github.com/derekparker/delve/service/debugger"
|
"github.com/derekparker/delve/service/debugger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerImpl struct {
|
|
||||||
s *RPCServer
|
|
||||||
}
|
|
||||||
|
|
||||||
type RPCServer struct {
|
type RPCServer struct {
|
||||||
// config is all the information necessary to start the debugger and server.
|
// config is all the information necessary to start the debugger and server.
|
||||||
config *service.Config
|
config *service.Config
|
||||||
// listener is used to serve HTTP.
|
|
||||||
listener net.Listener
|
|
||||||
// stopChan is used to stop the listener goroutine
|
|
||||||
stopChan chan struct{}
|
|
||||||
// debugger is a debugger service.
|
// debugger is a debugger service.
|
||||||
debugger *debugger.Debugger
|
debugger *debugger.Debugger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer creates a new RPCServer.
|
func NewServer(config *service.Config, debugger *debugger.Debugger) *RPCServer {
|
||||||
func NewServer(config *service.Config, logEnabled bool) *ServerImpl {
|
return &RPCServer{config, debugger}
|
||||||
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
|
||||||
if !logEnabled {
|
|
||||||
log.SetOutput(ioutil.Discard)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ServerImpl{
|
|
||||||
&RPCServer{
|
|
||||||
config: config,
|
|
||||||
listener: config.Listener,
|
|
||||||
stopChan: make(chan struct{}),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop detaches from the debugger and waits for it to stop.
|
|
||||||
func (s *ServerImpl) Stop(kill bool) error {
|
|
||||||
if s.s.config.AcceptMulti {
|
|
||||||
close(s.s.stopChan)
|
|
||||||
s.s.listener.Close()
|
|
||||||
}
|
|
||||||
err := s.s.debugger.Detach(kill)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run starts a debugger and exposes it with an HTTP server. The debugger
|
|
||||||
// itself can be stopped with the `detach` API. Run blocks until the HTTP
|
|
||||||
// server stops.
|
|
||||||
func (s *ServerImpl) Run() error {
|
|
||||||
var err error
|
|
||||||
// Create and start the debugger
|
|
||||||
if s.s.debugger, err = debugger.New(&debugger.Config{
|
|
||||||
ProcessArgs: s.s.config.ProcessArgs,
|
|
||||||
AttachPid: s.s.config.AttachPid,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rpcs := grpc.NewServer()
|
|
||||||
rpcs.Register(s.s)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer s.s.listener.Close()
|
|
||||||
for {
|
|
||||||
c, err := s.s.listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
select {
|
|
||||||
case <-s.s.stopChan:
|
|
||||||
// We were supposed to exit, do nothing and return
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
go rpcs.ServeCodec(jsonrpc.NewServerCodec(c))
|
|
||||||
if !s.s.config.AcceptMulti {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServerImpl) Restart() error {
|
|
||||||
return s.s.Restart(RestartIn{}, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProcessPidIn struct {
|
type ProcessPidIn struct {
|
||||||
@ -161,13 +81,16 @@ type CommandOut struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Command interrupts, continues and steps through the program.
|
// Command interrupts, continues and steps through the program.
|
||||||
func (s *RPCServer) Command(command api.DebuggerCommand, out *CommandOut) error {
|
func (s *RPCServer) Command(command api.DebuggerCommand, cb service.RPCCallback) {
|
||||||
st, err := s.debugger.Command(&command)
|
st, err := s.debugger.Command(&command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
cb.Return(nil, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
var out CommandOut
|
||||||
out.State = *st
|
out.State = *st
|
||||||
return nil
|
cb.Return(out, nil)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetBreakpointIn struct {
|
type GetBreakpointIn struct {
|
||||||
|
|||||||
6
service/rpccallback.go
Normal file
6
service/rpccallback.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
// RPCCallback is used by RPC methods to return their result asynchronously.
|
||||||
|
type RPCCallback interface {
|
||||||
|
Return(out interface{}, err error)
|
||||||
|
}
|
||||||
351
service/rpccommon/server.go
Normal file
351
service/rpccommon/server.go
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
package rpccommon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/rpc"
|
||||||
|
"net/rpc/jsonrpc"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/derekparker/delve/service"
|
||||||
|
"github.com/derekparker/delve/service/api"
|
||||||
|
"github.com/derekparker/delve/service/debugger"
|
||||||
|
"github.com/derekparker/delve/service/rpc1"
|
||||||
|
"github.com/derekparker/delve/service/rpc2"
|
||||||
|
"github.com/derekparker/delve/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServerImpl implements a JSON-RPC server that can switch between two
|
||||||
|
// versions of the API.
|
||||||
|
type ServerImpl struct {
|
||||||
|
// config is all the information necessary to start the debugger and server.
|
||||||
|
config *service.Config
|
||||||
|
// listener is used to serve HTTP.
|
||||||
|
listener net.Listener
|
||||||
|
// stopChan is used to stop the listener goroutine.
|
||||||
|
stopChan chan struct{}
|
||||||
|
// debugger is the debugger service.
|
||||||
|
debugger *debugger.Debugger
|
||||||
|
// s1 is APIv1 server.
|
||||||
|
s1 *rpc1.RPCServer
|
||||||
|
// s2 is APIv2 server.
|
||||||
|
s2 *rpc2.RPCServer
|
||||||
|
// maps of served methods, one for each supported API.
|
||||||
|
methodMaps []map[string]*methodType
|
||||||
|
}
|
||||||
|
|
||||||
|
type RPCCallback struct {
|
||||||
|
s *ServerImpl
|
||||||
|
sending *sync.Mutex
|
||||||
|
codec rpc.ServerCodec
|
||||||
|
req rpc.Request
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPCServer implements the RPC method calls common to all versions of the API.
|
||||||
|
type RPCServer struct {
|
||||||
|
s *ServerImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
type methodType struct {
|
||||||
|
method reflect.Method
|
||||||
|
Rcvr reflect.Value
|
||||||
|
ArgType reflect.Type
|
||||||
|
ReplyType reflect.Type
|
||||||
|
Synchronous bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer creates a new RPCServer.
|
||||||
|
func NewServer(config *service.Config, logEnabled bool) *ServerImpl {
|
||||||
|
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
||||||
|
if !logEnabled {
|
||||||
|
log.SetOutput(ioutil.Discard)
|
||||||
|
}
|
||||||
|
if config.APIVersion < 2 {
|
||||||
|
log.Printf("Using API v1")
|
||||||
|
}
|
||||||
|
return &ServerImpl{
|
||||||
|
config: config,
|
||||||
|
listener: config.Listener,
|
||||||
|
stopChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the JSON-RPC server.
|
||||||
|
func (s *ServerImpl) Stop(kill bool) error {
|
||||||
|
if s.config.AcceptMulti {
|
||||||
|
close(s.stopChan)
|
||||||
|
s.listener.Close()
|
||||||
|
}
|
||||||
|
return s.debugger.Detach(kill)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart restarts the debugger.
|
||||||
|
func (s *ServerImpl) Restart() error {
|
||||||
|
if s.config.AttachPid != 0 {
|
||||||
|
return errors.New("cannot restart process Delve did not create")
|
||||||
|
}
|
||||||
|
return s.s2.Restart(rpc2.RestartIn{}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run starts a debugger and exposes it with an HTTP server. The debugger
|
||||||
|
// itself can be stopped with the `detach` API. Run blocks until the HTTP
|
||||||
|
// server stops.
|
||||||
|
func (s *ServerImpl) Run() error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if s.config.APIVersion < 2 {
|
||||||
|
s.config.APIVersion = 1
|
||||||
|
}
|
||||||
|
if s.config.APIVersion > 2 {
|
||||||
|
return fmt.Errorf("unknown API version")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and start the debugger
|
||||||
|
if s.debugger, err = debugger.New(&debugger.Config{
|
||||||
|
ProcessArgs: s.config.ProcessArgs,
|
||||||
|
AttachPid: s.config.AttachPid,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.s1 = rpc1.NewServer(s.config, s.debugger)
|
||||||
|
s.s2 = rpc2.NewServer(s.config, s.debugger)
|
||||||
|
|
||||||
|
rpcServer := &RPCServer{s}
|
||||||
|
|
||||||
|
s.methodMaps = make([]map[string]*methodType, 2)
|
||||||
|
|
||||||
|
s.methodMaps[0] = map[string]*methodType{}
|
||||||
|
s.methodMaps[1] = map[string]*methodType{}
|
||||||
|
suitableMethods(s.s1, s.methodMaps[0])
|
||||||
|
suitableMethods(rpcServer, s.methodMaps[0])
|
||||||
|
suitableMethods(s.s2, s.methodMaps[1])
|
||||||
|
suitableMethods(rpcServer, s.methodMaps[1])
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer s.listener.Close()
|
||||||
|
for {
|
||||||
|
c, err := s.listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case <-s.stopChan:
|
||||||
|
// We were supposed to exit, do nothing and return
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go s.serveJSONCodec(c)
|
||||||
|
if !s.config.AcceptMulti {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Precompute the reflect type for error. Can't use error directly
|
||||||
|
// because Typeof takes an empty interface value. This is annoying.
|
||||||
|
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
|
|
||||||
|
// Is this an exported - upper case - name?
|
||||||
|
func isExported(name string) bool {
|
||||||
|
rune, _ := utf8.DecodeRuneInString(name)
|
||||||
|
return unicode.IsUpper(rune)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this type exported or a builtin?
|
||||||
|
func isExportedOrBuiltinType(t reflect.Type) bool {
|
||||||
|
for t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
// PkgPath will be non-empty even for an exported type,
|
||||||
|
// so we need to check the type name as well.
|
||||||
|
return isExported(t.Name()) || t.PkgPath() == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fills methods map with the methods of receiver that should be made
|
||||||
|
// available through the RPC interface.
|
||||||
|
// These are all the public methods of rcvr that have one of those
|
||||||
|
// two signatures:
|
||||||
|
// func (rcvr ReceiverType) Method(in InputType, out *ReplyType) error
|
||||||
|
// func (rcvr ReceiverType) Method(in InputType, cb service.RPCCallback)
|
||||||
|
func suitableMethods(rcvr interface{}, methods map[string]*methodType) {
|
||||||
|
typ := reflect.TypeOf(rcvr)
|
||||||
|
rcvrv := reflect.ValueOf(rcvr)
|
||||||
|
sname := reflect.Indirect(rcvrv).Type().Name()
|
||||||
|
if sname == "" {
|
||||||
|
log.Printf("rpc.Register: no service name for type %s", typ)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for m := 0; m < typ.NumMethod(); m++ {
|
||||||
|
method := typ.Method(m)
|
||||||
|
mname := method.Name
|
||||||
|
mtype := method.Type
|
||||||
|
// method must be exported
|
||||||
|
if method.PkgPath != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Method needs three ins: (receive, *args, *reply) or (receiver, *args, *RPCCallback)
|
||||||
|
if mtype.NumIn() != 3 {
|
||||||
|
log.Println("method", mname, "has wrong number of ins:", mtype.NumIn())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// First arg need not be a pointer.
|
||||||
|
argType := mtype.In(1)
|
||||||
|
if !isExportedOrBuiltinType(argType) {
|
||||||
|
log.Println(mname, "argument type not exported:", argType)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
replyType := mtype.In(2)
|
||||||
|
synchronous := replyType.String() != "service.RPCCallback"
|
||||||
|
|
||||||
|
if synchronous {
|
||||||
|
// Second arg must be a pointer.
|
||||||
|
if replyType.Kind() != reflect.Ptr {
|
||||||
|
log.Println("method", mname, "reply type not a pointer:", replyType)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Reply type must be exported.
|
||||||
|
if !isExportedOrBuiltinType(replyType) {
|
||||||
|
log.Println("method", mname, "reply type not exported:", replyType)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method needs one out.
|
||||||
|
if mtype.NumOut() != 1 {
|
||||||
|
log.Println("method", mname, "has wrong number of outs:", mtype.NumOut())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The return type of the method must be error.
|
||||||
|
if returnType := mtype.Out(0); returnType != typeOfError {
|
||||||
|
log.Println("method", mname, "returns", returnType.String(), "not error")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Method needs zero outs.
|
||||||
|
if mtype.NumOut() != 0 {
|
||||||
|
log.Println("method", mname, "has wrong number of outs:", mtype.NumOut())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
methods[sname+"."+mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType, Synchronous: synchronous, Rcvr: rcvrv}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerImpl) serveJSONCodec(conn io.ReadWriteCloser) {
|
||||||
|
sending := new(sync.Mutex)
|
||||||
|
codec := jsonrpc.NewServerCodec(conn)
|
||||||
|
var req rpc.Request
|
||||||
|
var resp rpc.Response
|
||||||
|
for {
|
||||||
|
req = rpc.Request{}
|
||||||
|
err := codec.ReadRequestHeader(&req)
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
log.Println("rpc:", err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
mtype, ok := s.methodMaps[s.config.APIVersion-1][req.ServiceMethod]
|
||||||
|
if !ok {
|
||||||
|
log.Printf("rpc: can't find method %s", req.ServiceMethod)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var argv, replyv reflect.Value
|
||||||
|
|
||||||
|
// Decode the argument value.
|
||||||
|
argIsValue := false // if true, need to indirect before calling.
|
||||||
|
if mtype.ArgType.Kind() == reflect.Ptr {
|
||||||
|
argv = reflect.New(mtype.ArgType.Elem())
|
||||||
|
} else {
|
||||||
|
argv = reflect.New(mtype.ArgType)
|
||||||
|
argIsValue = true
|
||||||
|
}
|
||||||
|
// argv guaranteed to be a pointer now.
|
||||||
|
if err = codec.ReadRequestBody(argv.Interface()); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if argIsValue {
|
||||||
|
argv = argv.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if mtype.Synchronous {
|
||||||
|
replyv = reflect.New(mtype.ReplyType.Elem())
|
||||||
|
function := mtype.method.Func
|
||||||
|
returnValues := function.Call([]reflect.Value{mtype.Rcvr, argv, replyv})
|
||||||
|
errInter := returnValues[0].Interface()
|
||||||
|
errmsg := ""
|
||||||
|
if errInter != nil {
|
||||||
|
errmsg = errInter.(error).Error()
|
||||||
|
}
|
||||||
|
resp = rpc.Response{}
|
||||||
|
s.sendResponse(sending, &req, &resp, replyv.Interface(), codec, errmsg)
|
||||||
|
} else {
|
||||||
|
function := mtype.method.Func
|
||||||
|
ctl := &RPCCallback{s, sending, codec, req}
|
||||||
|
go function.Call([]reflect.Value{mtype.Rcvr, argv, reflect.ValueOf(ctl)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
codec.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A value sent as a placeholder for the server's response value when the server
|
||||||
|
// receives an invalid request. It is never decoded by the client since the Response
|
||||||
|
// contains an error when it is used.
|
||||||
|
var invalidRequest = struct{}{}
|
||||||
|
|
||||||
|
func (s *ServerImpl) sendResponse(sending *sync.Mutex, req *rpc.Request, resp *rpc.Response, reply interface{}, codec rpc.ServerCodec, errmsg string) {
|
||||||
|
resp.ServiceMethod = req.ServiceMethod
|
||||||
|
if errmsg != "" {
|
||||||
|
resp.Error = errmsg
|
||||||
|
reply = invalidRequest
|
||||||
|
}
|
||||||
|
resp.Seq = req.Seq
|
||||||
|
sending.Lock()
|
||||||
|
defer sending.Unlock()
|
||||||
|
err := codec.WriteResponse(resp, reply)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("rpc: writing response:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *RPCCallback) Return(out interface{}, err error) {
|
||||||
|
errmsg := ""
|
||||||
|
if err != nil {
|
||||||
|
errmsg = err.Error()
|
||||||
|
}
|
||||||
|
var resp rpc.Response
|
||||||
|
cb.s.sendResponse(cb.sending, &cb.req, &resp, out, cb.codec, errmsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersion returns the version of delve as well as the API version
|
||||||
|
// currently served.
|
||||||
|
func (s *RPCServer) GetVersion(args api.GetVersionIn, out *api.GetVersionOut) error {
|
||||||
|
out.DelveVersion = version.DelveVersion.String()
|
||||||
|
out.APIVersion = s.s.config.APIVersion
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changes version of the API being served.
|
||||||
|
func (s *RPCServer) SetApiVersion(args api.SetAPIVersionIn, out *api.SetAPIVersionOut) error {
|
||||||
|
if args.APIVersion < 2 {
|
||||||
|
args.APIVersion = 1
|
||||||
|
}
|
||||||
|
if args.APIVersion > 2 {
|
||||||
|
return fmt.Errorf("unknown API version")
|
||||||
|
}
|
||||||
|
s.s.config.APIVersion = args.APIVersion
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
// This program checks the types of the arguments of calls to
|
// This program checks the types of the arguments of calls to
|
||||||
// the API in service/rpc2/client.go (done using rpc2.(*Client).call)
|
// the API in service/rpc2/client.go (done using rpc2.(*Client).call)
|
||||||
// against the declared types of API methods in srvice/rpc2/server.go
|
// against the declared types of API methods in srvice/rpc2/server.go
|
||||||
@ -13,6 +14,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func findRPCDir() string {
|
func findRPCDir() string {
|
||||||
@ -180,11 +182,13 @@ func main() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(params.At(1).Type().String(), "/service.RPCCallback") {
|
||||||
if a, e := info.TypeOf(arg2), params.At(1).Type(); !types.AssignableTo(a, e) {
|
if a, e := info.TypeOf(arg2), params.At(1).Type(); !types.AssignableTo(a, e) {
|
||||||
log.Printf("%s: wrong type of second argument %s, expected %s", fset.Position(callx.Pos()), types.TypeString(a, qf), types.TypeString(e, qf))
|
log.Printf("%s: wrong type of second argument %s, expected %s", fset.Position(callx.Pos()), types.TypeString(a, qf), types.TypeString(e, qf))
|
||||||
errcount++
|
errcount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if clit, ok := arg1.(*ast.CompositeLit); ok {
|
if clit, ok := arg1.(*ast.CompositeLit); ok {
|
||||||
typ := params.At(0).Type()
|
typ := params.At(0).Type()
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/derekparker/delve/service"
|
"github.com/derekparker/delve/service"
|
||||||
"github.com/derekparker/delve/service/api"
|
"github.com/derekparker/delve/service/api"
|
||||||
"github.com/derekparker/delve/service/rpc1"
|
"github.com/derekparker/delve/service/rpc1"
|
||||||
|
"github.com/derekparker/delve/service/rpccommon"
|
||||||
)
|
)
|
||||||
|
|
||||||
func withTestClient1(name string, t *testing.T, fn func(c *rpc1.RPCClient)) {
|
func withTestClient1(name string, t *testing.T, fn func(c *rpc1.RPCClient)) {
|
||||||
@ -25,7 +26,7 @@ func withTestClient1(name string, t *testing.T, fn func(c *rpc1.RPCClient)) {
|
|||||||
t.Fatalf("couldn't start listener: %s\n", err)
|
t.Fatalf("couldn't start listener: %s\n", err)
|
||||||
}
|
}
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
server := rpc1.NewServer(&service.Config{
|
server := rpccommon.NewServer(&service.Config{
|
||||||
Listener: listener,
|
Listener: listener,
|
||||||
ProcessArgs: []string{protest.BuildFixture(name).Path},
|
ProcessArgs: []string{protest.BuildFixture(name).Path},
|
||||||
}, false)
|
}, false)
|
||||||
@ -46,7 +47,7 @@ func Test1RunWithInvalidPath(t *testing.T) {
|
|||||||
t.Fatalf("couldn't start listener: %s\n", err)
|
t.Fatalf("couldn't start listener: %s\n", err)
|
||||||
}
|
}
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
server := rpc1.NewServer(&service.Config{
|
server := rpccommon.NewServer(&service.Config{
|
||||||
Listener: listener,
|
Listener: listener,
|
||||||
ProcessArgs: []string{"invalid_path"},
|
ProcessArgs: []string{"invalid_path"},
|
||||||
}, false)
|
}, false)
|
||||||
@ -134,7 +135,7 @@ func Test1Restart_duringStop(t *testing.T) {
|
|||||||
func Test1Restart_attachPid(t *testing.T) {
|
func Test1Restart_attachPid(t *testing.T) {
|
||||||
// Assert it does not work and returns error.
|
// Assert it does not work and returns error.
|
||||||
// We cannot restart a process we did not spawn.
|
// We cannot restart a process we did not spawn.
|
||||||
server := rpc1.NewServer(&service.Config{
|
server := rpccommon.NewServer(&service.Config{
|
||||||
Listener: nil,
|
Listener: nil,
|
||||||
AttachPid: 999,
|
AttachPid: 999,
|
||||||
}, false)
|
}, false)
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/derekparker/delve/service"
|
"github.com/derekparker/delve/service"
|
||||||
"github.com/derekparker/delve/service/api"
|
"github.com/derekparker/delve/service/api"
|
||||||
"github.com/derekparker/delve/service/rpc2"
|
"github.com/derekparker/delve/service/rpc2"
|
||||||
|
"github.com/derekparker/delve/service/rpccommon"
|
||||||
)
|
)
|
||||||
|
|
||||||
var normalLoadConfig = api.LoadConfig{true, 1, 64, 64, -1}
|
var normalLoadConfig = api.LoadConfig{true, 1, 64, 64, -1}
|
||||||
@ -32,7 +33,7 @@ func withTestClient2(name string, t *testing.T, fn func(c service.Client)) {
|
|||||||
t.Fatalf("couldn't start listener: %s\n", err)
|
t.Fatalf("couldn't start listener: %s\n", err)
|
||||||
}
|
}
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
server := rpc2.NewServer(&service.Config{
|
server := rpccommon.NewServer(&service.Config{
|
||||||
Listener: listener,
|
Listener: listener,
|
||||||
ProcessArgs: []string{protest.BuildFixture(name).Path},
|
ProcessArgs: []string{protest.BuildFixture(name).Path},
|
||||||
}, false)
|
}, false)
|
||||||
@ -53,9 +54,10 @@ func TestRunWithInvalidPath(t *testing.T) {
|
|||||||
t.Fatalf("couldn't start listener: %s\n", err)
|
t.Fatalf("couldn't start listener: %s\n", err)
|
||||||
}
|
}
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
server := rpc2.NewServer(&service.Config{
|
server := rpccommon.NewServer(&service.Config{
|
||||||
Listener: listener,
|
Listener: listener,
|
||||||
ProcessArgs: []string{"invalid_path"},
|
ProcessArgs: []string{"invalid_path"},
|
||||||
|
APIVersion: 2,
|
||||||
}, false)
|
}, false)
|
||||||
if err := server.Run(); err == nil {
|
if err := server.Run(); err == nil {
|
||||||
t.Fatal("Expected Run to return error for invalid program path")
|
t.Fatal("Expected Run to return error for invalid program path")
|
||||||
@ -141,9 +143,10 @@ func TestRestart_duringStop(t *testing.T) {
|
|||||||
func TestRestart_attachPid(t *testing.T) {
|
func TestRestart_attachPid(t *testing.T) {
|
||||||
// Assert it does not work and returns error.
|
// Assert it does not work and returns error.
|
||||||
// We cannot restart a process we did not spawn.
|
// We cannot restart a process we did not spawn.
|
||||||
server := rpc2.NewServer(&service.Config{
|
server := rpccommon.NewServer(&service.Config{
|
||||||
Listener: nil,
|
Listener: nil,
|
||||||
AttachPid: 999,
|
AttachPid: 999,
|
||||||
|
APIVersion: 2,
|
||||||
}, false)
|
}, false)
|
||||||
if err := server.Restart(); err == nil {
|
if err := server.Restart(); err == nil {
|
||||||
t.Fatal("expected error on restart after attaching to pid but got none")
|
t.Fatal("expected error on restart after attaching to pid but got none")
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/derekparker/delve/proc/test"
|
"github.com/derekparker/delve/proc/test"
|
||||||
"github.com/derekparker/delve/service"
|
"github.com/derekparker/delve/service"
|
||||||
"github.com/derekparker/delve/service/api"
|
"github.com/derekparker/delve/service/api"
|
||||||
|
"github.com/derekparker/delve/service/rpccommon"
|
||||||
"github.com/derekparker/delve/service/rpc2"
|
"github.com/derekparker/delve/service/rpc2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -77,7 +78,7 @@ func withTestTerminal(name string, t testing.TB, fn func(*FakeTerminal)) {
|
|||||||
t.Fatalf("couldn't start listener: %s\n", err)
|
t.Fatalf("couldn't start listener: %s\n", err)
|
||||||
}
|
}
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
server := rpc2.NewServer(&service.Config{
|
server := rpccommon.NewServer(&service.Config{
|
||||||
Listener: listener,
|
Listener: listener,
|
||||||
ProcessArgs: []string{test.BuildFixture(name).Path},
|
ProcessArgs: []string{test.BuildFixture(name).Path},
|
||||||
}, false)
|
}, false)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user