From da62de8520f3e9c136cd03c53655e8af00fb33a3 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 5 Aug 2020 08:41:30 +0200 Subject: [PATCH 1/2] deps: Bump creachadair/jrpc2 to latest (0.10.0) --- go.mod | 6 +- go.sum | 12 +- vendor/github.com/creachadair/jrpc2/base.go | 118 +++++------ vendor/github.com/creachadair/jrpc2/client.go | 83 +++++--- vendor/github.com/creachadair/jrpc2/ctx.go | 31 ++- vendor/github.com/creachadair/jrpc2/doc.go | 38 +++- vendor/github.com/creachadair/jrpc2/go.mod | 4 +- vendor/github.com/creachadair/jrpc2/go.sum | 8 +- vendor/github.com/creachadair/jrpc2/opts.go | 54 ++++- vendor/github.com/creachadair/jrpc2/server.go | 191 +++++++++++++----- .../github.com/google/go-cmp/cmp/compare.go | 4 + .../google/go-cmp/cmp/report_compare.go | 17 +- .../google/go-cmp/cmp/report_reflect.go | 23 ++- .../google/go-cmp/cmp/report_slices.go | 4 +- .../golang.org/x/sync/semaphore/semaphore.go | 11 +- vendor/modules.txt | 6 +- 16 files changed, 409 insertions(+), 201 deletions(-) diff --git a/go.mod b/go.mod index a93286048..3dd28fcf8 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.13 require ( github.com/apparentlymart/go-textseg v1.0.0 - github.com/creachadair/jrpc2 v0.8.1 + github.com/creachadair/jrpc2 v0.10.0 github.com/fsnotify/fsnotify v1.4.9 - github.com/google/go-cmp v0.4.0 + github.com/google/go-cmp v0.4.1 github.com/hashicorp/go-multierror v1.1.0 github.com/hashicorp/go-version v1.2.0 github.com/hashicorp/hcl/v2 v2.5.2-0.20200528183353-fa7c453538de @@ -21,7 +21,7 @@ require ( github.com/spf13/afero v1.3.2 github.com/zclconf/go-cty v1.2.1 golang.org/x/net v0.0.0-20191009170851-d66e71096ffb - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e + golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 // indirect ) diff --git a/go.sum b/go.sum index 486c49535..df0879e84 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/creachadair/jrpc2 v0.8.1 h1:YX27WgtXa9v7g0kn3oYe7gmxt7e5M3miVgEgTXoTqcY= -github.com/creachadair/jrpc2 v0.8.1/go.mod h1:yMFI6NwXGITWoWDEEdmZESIHp13SAuSkGCTBz9bSGBg= +github.com/creachadair/jrpc2 v0.10.0 h1:mGfesLMmnbgFBqcbnowkn5CY1DTzl+S7lektnzFvJuE= +github.com/creachadair/jrpc2 v0.10.0/go.mod h1:+3c/p6ON1SXme/oF6hX4/EODB+GIQ+e7FGPizh27VQQ= github.com/creachadair/staticfile v0.1.2/go.mod h1:a3qySzCIXEprDGxk6tSxSI+dBBdLzqeBOMhZ+o2d3pM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -29,8 +29,8 @@ github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3a github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -103,8 +103,8 @@ golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/creachadair/jrpc2/base.go b/vendor/github.com/creachadair/jrpc2/base.go index 9b9f19dfa..8e501a9af 100644 --- a/vendor/github.com/creachadair/jrpc2/base.go +++ b/vendor/github.com/creachadair/jrpc2/base.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" "strings" @@ -102,7 +101,7 @@ var ErrInvalidVersion = Errorf(code.InvalidRequest, "incorrect version marker") // returns ErrInvalidVersion along with the parsed results. Otherwise, no // validation apart from basic structure is performed on the results. func ParseRequests(msg []byte) ([]*Request, error) { - var req jrequests + var req jmessages if err := req.parseJSON(msg); err != nil { return nil, err } @@ -131,7 +130,7 @@ type Response struct { // ch completes the request and is responsible for updating rsp and then // closing ch. The client owns writing to ch, and is responsible to ensure // that at most one write is ever performed. - ch chan *jresponse + ch chan *jmessage cancel func() } @@ -160,7 +159,7 @@ func (r *Response) ResultString() string { return string(r.result) } // MarshalJSON converts the response to equivalent JSON. func (r *Response) MarshalJSON() ([]byte, error) { - return json.Marshal(&jresponse{ + return json.Marshal(&jmessage{ V: Version, ID: json.RawMessage(r.id), R: r.result, @@ -193,20 +192,20 @@ func (r *Response) wait() { } } -// jrequests is either a single request or a slice of requests. This handles -// the decoding of batch requests in JSON-RPC 2.0. -type jrequests []*jrequest +// jmessages is either a single protocol message or an array of protocol +// messages. This handles the decoding of batch requests in JSON-RPC 2.0. +type jmessages []*jmessage -func (j jrequests) toJSON() ([]byte, error) { - if len(j) == 1 { +func (j jmessages) toJSON() ([]byte, error) { + if len(j) == 1 && !j[0].batch { return json.Marshal(j[0]) } - return json.Marshal([]*jrequest(j)) + return json.Marshal([]*jmessage(j)) } // N.B. Not UnmarshalJSON, because json.Unmarshal checks for validity early and // here we want to control the error that is returned. -func (j *jrequests) parseJSON(data []byte) error { +func (j *jmessages) parseJSON(data []byte) error { *j = (*j)[:0] // reset state // When parsing requests, validation checks are deferred: The only immediate @@ -228,7 +227,7 @@ func (j *jrequests) parseJSON(data []byte) error { // Now parse the individual request messages, but do not fail on errors. We // know that the messages are intact, but validity is checked at usage. for _, raw := range msgs { - req := new(jrequest) + req := new(jmessage) req.parseJSON(raw) req.batch = batch *j = append(*j, req) @@ -236,23 +235,33 @@ func (j *jrequests) parseJSON(data []byte) error { return nil } -// jrequest is the transmission format of a request message. -type jrequest struct { +// jmessage is the transmission format of a protocol message. +type jmessage struct { V string `json:"jsonrpc"` // must be Version ID json.RawMessage `json:"id,omitempty"` // may be nil - M string `json:"method"` - P json.RawMessage `json:"params,omitempty"` // may be nil - batch bool // this request was part of a batch - err error // if not nil, this request is invalid and err is why + // Fields belonging to request or notification objects + M string `json:"method,omitempty"` + P json.RawMessage `json:"params,omitempty"` // may be nil + + // Fields belonging to response or error objects + E *Error `json:"error,omitempty"` // set on error + R json.RawMessage `json:"result,omitempty"` // set on success + + // N.B.: In a valid protocol message, M and P are mutually exclusive with E + // and R. Specifically, if M != "" then E and R must both be unset. This is + // checked during parsing. + + batch bool // this message was part of a batch + err error // if not nil, this message is invalid and err is why } -func (j *jrequest) fail(code code.Code, msg string) error { +func (j *jmessage) fail(code code.Code, msg string) error { j.err = Errorf(code, msg) return j.err } -func (j *jrequest) parseJSON(data []byte) error { +func (j *jmessage) parseJSON(data []byte) error { // Unmarshal into a map so we can check for extra keys. The json.Decoder // has DisallowUnknownFields, but fails decoding eagerly for fields that do // not map to known tags. We want to fully parse the object so we can @@ -264,7 +273,7 @@ func (j *jrequest) parseJSON(data []byte) error { return j.fail(code.ParseError, "request is not a JSON object") } - *j = jrequest{} // reset content + *j = jmessage{} // reset content var extra []string // extra field names for key, val := range obj { switch key { @@ -287,55 +296,34 @@ func (j *jrequest) parseJSON(data []byte) error { if len(j.P) != 0 && j.P[0] != '[' && j.P[0] != '{' { j.fail(code.InvalidRequest, "parameters must be array or object") } + case "error": + if json.Unmarshal(val, &j.E) != nil { + j.fail(code.ParseError, "invalid error value") + } + case "result": + j.R = val default: extra = append(extra, key) } } + // Report an error if request/response fields overlap. + if j.M != "" && (j.E != nil || j.R != nil) { + j.fail(code.InvalidRequest, "mixed request and reply fields") + } + // Report an error for extraneous fields. - if len(extra) != 0 { + if j.err == nil && len(extra) != 0 { j.err = DataErrorf(code.InvalidRequest, extra, "extra fields in request") } return nil } -// jresponses is a slice of responses, encoded as a single response if there is -// exactly one. -type jresponses []*jresponse - -func (j jresponses) toJSON() ([]byte, error) { - if len(j) == 1 && !j[0].batch { - return json.Marshal(j[0]) - } - return json.Marshal([]*jresponse(j)) -} - -func (j *jresponses) parseJSON(data []byte) error { - if len(data) == 0 { - return errors.New("empty request message") - } else if data[0] != '[' { - *j = jresponses{new(jresponse)} - return json.Unmarshal(data, (*j)[0]) - } - return json.Unmarshal(data, (*[]*jresponse)(j)) -} - -// jresponse is the transmission format of a response message. -type jresponse struct { - V string `json:"jsonrpc"` // must be Version - ID json.RawMessage `json:"id,omitempty"` // set if request had an ID - E *Error `json:"error,omitempty"` // set on error - R json.RawMessage `json:"result,omitempty"` // set on success - - // Allow the server to send a response that looks like a notification. - // This is an extension of JSON-RPC 2.0. - M string `json:"method,omitempty"` - P json.RawMessage `json:"params,omitempty"` - - batch bool // the request was part of a batch -} +// isRequestOrNotification reports whether j is a request or notification. +func (j *jmessage) isRequestOrNotification() bool { return j.E == nil && j.R == nil && j.M != "" } -func (j jresponse) isServerRequest() bool { return j.E == nil && j.R == nil && j.M != "" } +// isNotification reports whether j is a notification +func (j *jmessage) isNotification() bool { return j.isRequestOrNotification() && fixID(j.ID) == nil } type jerror struct { C int32 `json:"code"` @@ -354,7 +342,7 @@ func fixID(id json.RawMessage) json.RawMessage { } // encode marshals rsps as JSON and forwards it to the channel. -func encode(ch channel.Sender, rsps jresponses) (int, error) { +func encode(ch channel.Sender, rsps jmessages) (int, error) { bits, err := rsps.toJSON() if err != nil { return 0, err @@ -403,3 +391,15 @@ func isServiceName(s string) bool { func isNull(msg json.RawMessage) bool { return len(msg) == 4 && msg[0] == 'n' && msg[1] == 'u' && msg[2] == 'l' && msg[3] == 'l' } + +// filterError filters an *Error value to distinguish context errors from other +// error types. If err is not a context error, it is returned unchanged. +func filterError(e *Error) error { + switch e.code { + case code.Cancelled: + return context.Canceled + case code.DeadlineExceeded: + return context.DeadlineExceeded + } + return e +} diff --git a/vendor/github.com/creachadair/jrpc2/client.go b/vendor/github.com/creachadair/jrpc2/client.go index 6924ef3d4..780b5b1d2 100644 --- a/vendor/github.com/creachadair/jrpc2/client.go +++ b/vendor/github.com/creachadair/jrpc2/client.go @@ -18,9 +18,11 @@ import ( type Client struct { done chan struct{} // closed when the reader is done at shutdown time - log func(string, ...interface{}) // write debug logs here - enctx encoder - snote func(*jresponse) bool + log func(string, ...interface{}) // write debug logs here + enctx encoder + snote func(*jmessage) + scall func(*jmessage) ([]byte, error) + allow1 bool // tolerate v1 replies with no version marker allowC bool // send rpc.cancel when a request context ends @@ -40,6 +42,7 @@ func NewClient(ch channel.Channel, opts *ClientOptions) *Client { allowC: opts.allowCancel(), enctx: opts.encodeContext(), snote: opts.handleNotification(), + scall: opts.handleCallback(), // Lock-protected fields ch: ch, @@ -63,10 +66,10 @@ func NewClient(ch channel.Channel, opts *ClientOptions) *Client { } // accept receives the next batch of responses from the server. This may -// either be a list or a single object, the decoder for jresponses knows how to +// either be a list or a single object, the decoder for jmessages knows how to // handle both. The caller must not hold c.mu. func (c *Client) accept(ch channel.Receiver) error { - var in jresponses + var in jmessages bits, err := ch.Recv() if err == nil { err = in.parseJSON(bits) @@ -89,21 +92,42 @@ func (c *Client) accept(ch channel.Receiver) error { return nil } +// handleRequest handles a callback or notification from the server. The +// caller must hold c.mu, and this blocks until the handler completes. +// Precondition: msg is a request or notification, not a response or error. +func (c *Client) handleRequest(msg *jmessage) { + if msg.isNotification() { + if c.snote == nil { + c.log("Discarding notification: %v", msg) + } else { + c.snote(msg) + } + } else if c.scall == nil { + c.log("Discarding callback request: %v", msg) + } else if bits, err := c.scall(msg); err != nil { + c.log("Callback for %v failed: %v", msg, err) + } else if err := c.ch.Send(bits); err != nil { + c.log("Sending reply for callback %v failed: %v", msg, err) + } +} + // For each response, find the request pending on its ID and deliver it. The // caller must hold c.mu. Unknown response IDs are logged and discarded. As // we are under the lock, we do not wait for the pending receiver to pick up // the response; we just drop it in their channel. The channel is buffered so // we don't need to rendezvous. -func (c *Client) deliver(rsp *jresponse) { - if id := string(fixID(rsp.ID)); id == "" { - if !c.snote(rsp) { - c.log("Discarding response without ID: %v", rsp) - } - } else if p := c.pending[id]; p == nil { +func (c *Client) deliver(rsp *jmessage) { + if rsp.isRequestOrNotification() { + c.handleRequest(rsp) + return + } + + id := string(fixID(rsp.ID)) + if p := c.pending[id]; p == nil { c.log("Discarding response for unknown ID %q", id) } else if !c.versionOK(rsp.V) { delete(c.pending, id) - p.ch <- &jresponse{ + p.ch <- &jmessage{ ID: rsp.ID, E: &Error{ code: code.InvalidRequest, @@ -122,7 +146,7 @@ func (c *Client) deliver(rsp *jresponse) { // req constructs a fresh request for the specified method and parameters. // This does not transmit the request to the server; use c.send to do so. -func (c *Client) req(ctx context.Context, method string, params interface{}) (*jrequest, error) { +func (c *Client) req(ctx context.Context, method string, params interface{}) (*jmessage, error) { bits, err := c.marshalParams(ctx, method, params) if err != nil { return nil, err @@ -132,7 +156,7 @@ func (c *Client) req(ctx context.Context, method string, params interface{}) (*j defer c.mu.Unlock() id := json.RawMessage(strconv.FormatInt(c.nextID, 10)) c.nextID++ - return &jrequest{ + return &jmessage{ V: Version, ID: id, M: method, @@ -141,12 +165,12 @@ func (c *Client) req(ctx context.Context, method string, params interface{}) (*j } // note constructs a notification request for the specified method and parameters. -func (c *Client) note(ctx context.Context, method string, params interface{}) (*jrequest, error) { +func (c *Client) note(ctx context.Context, method string, params interface{}) (*jmessage, error) { bits, err := c.marshalParams(ctx, method, params) if err != nil { return nil, err } - return &jrequest{V: Version, M: method, P: bits}, nil + return &jmessage{V: Version, M: method, P: bits}, nil } // send transmits the specified requests to the server and returns a slice of @@ -157,7 +181,7 @@ func (c *Client) note(ctx context.Context, method string, params interface{}) (* // the requests are notifications, the slice will be empty. // // This method blocks until the entire batch of requests has been transmitted. -func (c *Client) send(ctx context.Context, reqs jrequests) ([]*Response, error) { +func (c *Client) send(ctx context.Context, reqs jmessages) ([]*Response, error) { if len(reqs) == 0 { return nil, errors.New("empty request batch") } @@ -228,7 +252,7 @@ func (c *Client) waitComplete(pctx context.Context, id string, p *Response) { jerr = &Error{code: code.FromError(err), message: err.Error()} } - p.ch <- &jresponse{ + p.ch <- &jmessage{ ID: json.RawMessage(id), E: jerr, } @@ -243,9 +267,9 @@ func (c *Client) waitComplete(pctx context.Context, id string, p *Response) { } } -// Call initiates a single request and blocks until the response returns. If -// err != nil then rsp == nil, which also means that if rsp != nil then the -// request succeeded. Errors from the server have concrete type *jrpc2.Error. +// Call initiates a single request and blocks until the response returns. +// A successful call reports a nil error and a non-nil response. Errors from +// the server have concrete type *jrpc2.Error. // // rsp, err := c.Call(ctx, method, params) // if e, ok := err.(*jrpc2.Error); ok { @@ -260,20 +284,13 @@ func (c *Client) Call(ctx context.Context, method string, params interface{}) (* if err != nil { return nil, err } - rsp, err := c.send(ctx, jrequests{req}) + rsp, err := c.send(ctx, jmessages{req}) if err != nil { return nil, err } rsp[0].wait() if err := rsp[0].Error(); err != nil { - switch err.code { - case code.Cancelled: - return nil, context.Canceled - case code.DeadlineExceeded: - return nil, context.DeadlineExceeded - default: - return nil, err - } + return nil, filterError(err) } return rsp[0], nil } @@ -296,7 +313,7 @@ func (c *Client) CallResult(ctx context.Context, method string, params, result i // Any error returned is from sending the batch; the caller must check each // response for errors from the server. func (c *Client) Batch(ctx context.Context, specs []Spec) ([]*Response, error) { - reqs := make(jrequests, len(specs)) + reqs := make(jmessages, len(specs)) for i, spec := range specs { if spec.Notify { req, err := c.note(ctx, spec.Method, spec.Params) @@ -335,7 +352,7 @@ func (c *Client) Notify(ctx context.Context, method string, params interface{}) if err != nil { return err } - _, err = c.send(ctx, jrequests{req}) + _, err = c.send(ctx, jmessages{req}) return err } @@ -407,7 +424,7 @@ func newPending(ctx context.Context, id string) (context.Context, *Response) { // with the recipient. pctx, cancel := context.WithCancel(ctx) return pctx, &Response{ - ch: make(chan *jresponse, 1), + ch: make(chan *jmessage, 1), id: id, cancel: cancel, } diff --git a/vendor/github.com/creachadair/jrpc2/ctx.go b/vendor/github.com/creachadair/jrpc2/ctx.go index e62369df3..c5d5e5219 100644 --- a/vendor/github.com/creachadair/jrpc2/ctx.go +++ b/vendor/github.com/creachadair/jrpc2/ctx.go @@ -32,16 +32,31 @@ func InboundRequest(ctx context.Context) *Request { type inboundRequestKey struct{} -// ServerPush posts a server notification to the client. If ctx does not -// contain a server notifier, this reports ErrNotifyUnsupported. The context +// PushNotify posts a server notification to the client. If ctx does not +// contain a server notifier, this reports ErrPushUnsupported. The context // passed to the handler by *jrpc2.Server will support notifications if the // server was constructed with the AllowPush option set true. -func ServerPush(ctx context.Context, method string, params interface{}) error { +func PushNotify(ctx context.Context, method string, params interface{}) error { s := ctx.Value(serverKey{}).(*Server) if !s.allowP { - return ErrNotifyUnsupported + return ErrPushUnsupported } - return s.Push(ctx, method, params) + return s.Notify(ctx, method, params) +} + +// PushCall posts a server call to the client. If ctx does not contain a server +// caller, this reports ErrPushUnsupported. The context passed to the handler +// by *jrpc2.Server will support callbacks if the server was constructed with +// the AllowPush option set true. +// +// A successful callback reports a nil error and a non-nil response. Errors +// returned by the client have concrete type *jrpc2.Error. +func PushCall(ctx context.Context, method string, params interface{}) (*Response, error) { + s := ctx.Value(serverKey{}).(*Server) + if !s.allowP { + return nil, ErrPushUnsupported + } + return s.Callback(ctx, method, params) } // CancelRequest requests the cancellation of the pending or in-flight request @@ -54,6 +69,6 @@ func CancelRequest(ctx context.Context, id string) { type serverKey struct{} -// ErrNotifyUnsupported is returned by ServerPush if server notifications are -// not enabled in the specified context. -var ErrNotifyUnsupported = errors.New("server notifications are not enabled") +// ErrPushUnsupported is returned by PushNotify and PushCall if server pushes +// are not enabled in the specified context. +var ErrPushUnsupported = errors.New("server push is not enabled") diff --git a/vendor/github.com/creachadair/jrpc2/doc.go b/vendor/github.com/creachadair/jrpc2/doc.go index 4afdc8c04..44f2b20b4 100644 --- a/vendor/github.com/creachadair/jrpc2/doc.go +++ b/vendor/github.com/creachadair/jrpc2/doc.go @@ -185,6 +185,21 @@ name on the first period ("."), and you may nest ServiceMaps more deeply if you require a more complex hierarchy. +Concurrency + +A Server processes requests concurrently, up to the Concurrency limit given in +its ServerOptions. Two requests (calls or notifications) are concurrent if they +arrive as part of the same batch. In addition, two calls are concurrent if the +time intervals between the arrival of the request objects and delivery of the +response objects overlap. + +The server may issue concurrent requests to their handlers in any order. +Otherwise, requests are processed in order of arrival. Notifications, in +particular, can only be concurrent with other notifications in the same batch. +This ensures a client that sends a notification can be sure its notification +was fully processed before any subsequent calls are issued. + + Non-Standard Extension Methods By default a jrpc2.Server exports the following built-in non-standard extension @@ -203,20 +218,25 @@ These extension methods are enabled by default, but may be disabled by setting the DisableBuiltin server option to true when constructing the server. -Server Notifications +Server Push The AllowPush option in jrpc2.ServerOptions enables the server to "push" -notifications back to the client. This is a non-standard extension of JSON-RPC -used by some applications such as the Language Server Protocol (LSP). The Push -method sends a notification back to the client, if this feature is enabled: +requests back to the client. This is a non-standard extension of JSON-RPC used +by some applications such as the Language Server Protocol (LSP). If this +feature is enabled, the server's Notify and Callback methods send requests back +to the client: - if err := s.Push(ctx, "methodName", params); err == jrpc2.ErrNotifyUnsupported { - // server notifications are not enabled + if err := s.Notify(ctx, "methodName", params); err == jrpc2.ErrPushUnsupported { + // server push is not enabled + } + if rsp, err := s.Callback(ctx, "methodName", params); err == jrpc2.ErrPushUnsupported { + // server push is not enabled } -A method handler may use jrpc2.ServerPush to access this functionality. On the -client side, the OnNotify option in jrpc2.ClientOptions provides a callback to -which any server notifications are delivered if it is set. +A method handler may use jrpc2.PushNotify and jrpc2.PushCall functions to +access these methods. On the client side, the OnNotify and OnCallback options +in jrpc2.ClientOptions provide hooks to which any server requests are +delivered, if they are set. */ package jrpc2 diff --git a/vendor/github.com/creachadair/jrpc2/go.mod b/vendor/github.com/creachadair/jrpc2/go.mod index 0255c4b5c..74b46c850 100644 --- a/vendor/github.com/creachadair/jrpc2/go.mod +++ b/vendor/github.com/creachadair/jrpc2/go.mod @@ -3,8 +3,8 @@ module github.com/creachadair/jrpc2 require ( bitbucket.org/creachadair/shell v0.0.6 bitbucket.org/creachadair/stringset v0.0.8 - github.com/google/go-cmp v0.4.0 - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e + github.com/google/go-cmp v0.4.1 + golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a ) go 1.13 diff --git a/vendor/github.com/creachadair/jrpc2/go.sum b/vendor/github.com/creachadair/jrpc2/go.sum index ef80e41e6..0dab8f38e 100644 --- a/vendor/github.com/creachadair/jrpc2/go.sum +++ b/vendor/github.com/creachadair/jrpc2/go.sum @@ -4,9 +4,9 @@ bitbucket.org/creachadair/stringset v0.0.8 h1:gQqe4vs8XWgMyijfyKE6K8o4TcyGGrRXe0 bitbucket.org/creachadair/stringset v0.0.8/go.mod h1:AgthVMyMxC/6FK1KBJ2ALdqkZObGN8hOetgpwXyMn34= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/creachadair/staticfile v0.1.2/go.mod h1:a3qySzCIXEprDGxk6tSxSI+dBBdLzqeBOMhZ+o2d3pM= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/vendor/github.com/creachadair/jrpc2/opts.go b/vendor/github.com/creachadair/jrpc2/opts.go index 44ec1e888..7eb626d84 100644 --- a/vendor/github.com/creachadair/jrpc2/opts.go +++ b/vendor/github.com/creachadair/jrpc2/opts.go @@ -8,6 +8,7 @@ import ( "runtime" "time" + "github.com/creachadair/jrpc2/code" "github.com/creachadair/jrpc2/metrics" ) @@ -25,9 +26,9 @@ type ServerOptions struct { // required "jsonrpc" version marker. AllowV1 bool - // Instructs the server to allow server notifications, a non-standard - // extension to the JSON-RPC protocol. If AllowPush is false, the Push - // method of the server will report an error when called. + // Instructs the server to allow server callbacks and notifications, a + // non-standard extension to the JSON-RPC protocol. If AllowPush is false, + // the Notify and Callback methods of the server report errors if called. AllowPush bool // Instructs the server to disable the built-in rpc.* handler methods. @@ -38,7 +39,8 @@ type ServerOptions struct { DisableBuiltin bool // Allows up to the specified number of goroutines to execute concurrently - // in request handlers. A value less than 1 uses runtime.NumCPU(). + // in request handlers. A value less than 1 uses runtime.NumCPU(). Note + // that this setting does not constrain order of issue. Concurrency int // If set, this function is called with the method name and encoded request @@ -149,6 +151,12 @@ type ClientOptions struct { // most one invocation of the callback will be active at a time. // Server notifications are a non-standard extension of JSON-RPC. OnNotify func(*Request) + + // If set, this function is called if a request is received from the server. + // If unset, server requests are logged and discarded. At most one + // invocation of this callback will be active at a time. + // Server callbacks are a non-standard extension of JSON-RPC. + OnCallback func(context.Context, *Request) (interface{}, error) } func (c *ClientOptions) logger() logger { @@ -173,17 +181,41 @@ func (c *ClientOptions) encodeContext() encoder { return c.EncodeContext } -func (c *ClientOptions) handleNotification() func(*jresponse) bool { +func (c *ClientOptions) handleNotification() func(*jmessage) { if c == nil || c.OnNotify == nil { - return func(*jresponse) bool { return false } + return nil } h := c.OnNotify - return func(req *jresponse) bool { - if req.isServerRequest() { - h(&Request{method: req.M, params: req.P}) - return true + return func(req *jmessage) { h(&Request{method: req.M, params: req.P}) } +} + +func (c *ClientOptions) handleCallback() func(*jmessage) ([]byte, error) { + if c == nil || c.OnCallback == nil { + return nil + } + cb := c.OnCallback + return func(req *jmessage) ([]byte, error) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + rsp := &jmessage{V: Version, ID: req.ID} + v, err := cb(ctx, &Request{ + id: req.ID, + method: req.M, + params: req.P, + }) + if err == nil { + rsp.R, err = json.Marshal(v) + } + if err != nil { + rsp.R = nil + if e, ok := err.(*Error); ok { + rsp.E = e + } else { + rsp.E = &Error{code: code.FromError(err), message: err.Error()} + } } - return false + return json.Marshal(rsp) } } diff --git a/vendor/github.com/creachadair/jrpc2/server.go b/vendor/github.com/creachadair/jrpc2/server.go index 162df2405..fe12b8914 100644 --- a/vendor/github.com/creachadair/jrpc2/server.go +++ b/vendor/github.com/creachadair/jrpc2/server.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "io" + "strconv" "strings" "sync" "time" @@ -37,6 +38,7 @@ type Server struct { mu *sync.Mutex // protects the fields below + nbar sync.WaitGroup // notification barrier (see the dispatch method) err error // error from a previous operation work *sync.Cond // for signaling message availability inq *list.List // inbound requests awaiting processing @@ -45,6 +47,11 @@ type Server struct { // For each request ID currently in-flight, this map carries a cancel // function attached to the context that was sent to the handler. used map[string]context.CancelFunc + + // For each push-call ID currently in flight, this map carries the response + // waiting for its reply. + call map[string]*Response + callID int64 } // NewServer returns a new unstarted server that will dispatch incoming @@ -75,6 +82,8 @@ func NewServer(mux Assigner, opts *ServerOptions) *Server { builtin: opts.allowBuiltin(), inq: list.New(), used: make(map[string]context.CancelFunc), + call: make(map[string]*Response), + callID: 1, } s.work = sync.NewCond(s.mu) return s @@ -161,7 +170,7 @@ func (s *Server) nextRequest() (func() error, error) { } ch := s.ch // capture - next := s.inq.Remove(s.inq.Front()).(jrequests) + next := s.inq.Remove(s.inq.Front()).(jmessages) s.log("Processing %d requests", len(next)) // Construct a dispatcher to run the handlers outside the lock. @@ -171,25 +180,49 @@ func (s *Server) nextRequest() (func() error, error) { // dispatch constructs a function that invokes each of the specified tasks. // The caller must hold s.mu when calling dispatch, but the returned function // should be executed outside the lock to wait for the handlers to return. -func (s *Server) dispatch(next jrequests, ch channel.Sender) func() error { +// +// dispatch blocks until any notification received prior to this batch has +// completed, to ensure that notifications are processed in a partial order +// that respects order of receipt. Notifications within a batch are handled +// concurrently. +func (s *Server) dispatch(next jmessages, ch channel.Sender) func() error { // Resolve all the task handlers or record errors. start := time.Now() tasks := s.checkAndAssign(next) - var wg sync.WaitGroup - for _, t := range tasks { - if t.err != nil { - continue // nothing to do here; this task has already failed - } - t := t - wg.Add(1) - go func() { - defer wg.Done() - t.val, t.err = s.invoke(t.ctx, t.m, t.hreq) - }() - } + last := len(tasks) - 1 + + // s.nbar counts the number of notifications that have been issued and are + // not yet complete. Before issuing any tasks in this batch, wait for all + // such notifications to complete, and update the barrier with the number in + // this batch. This update must happen under s.mu, so that multiple batches + // do not race on Wait/Add. + s.nbar.Wait() + s.nbar.Add(tasks.numValidNotifications()) - // Wait for all the handlers to return, then deliver any responses. return func() error { + var wg sync.WaitGroup + for i, t := range tasks { + if t.err != nil { + continue // nothing to do here; this task has already failed + } + t := t + + wg.Add(1) + run := func() { + defer wg.Done() + if t.hreq.IsNotification() { + defer s.nbar.Done() + } + t.val, t.err = s.invoke(t.ctx, t.m, t.hreq) + } + if i < last { + go run() + } else { + run() + } + } + + // Wait for all the handlers to return, then deliver any responses. wg.Wait() return s.deliver(tasks.responses(s.rpcLog), ch, time.Since(start)) } @@ -197,7 +230,7 @@ func (s *Server) dispatch(next jrequests, ch channel.Sender) func() error { // deliver cleans up completed responses and arranges their replies (if any) to // be sent back to the client. -func (s *Server) deliver(rsps jresponses, ch channel.Sender, elapsed time.Duration) error { +func (s *Server) deliver(rsps jmessages, ch channel.Sender, elapsed time.Duration) error { if len(rsps) == 0 { return nil } @@ -217,7 +250,7 @@ func (s *Server) deliver(rsps jresponses, ch channel.Sender, elapsed time.Durati // checkAndAssign resolves all the task handlers for the given batch, or // records errors for them as appropriate. The caller must hold s.mu. -func (s *Server) checkAndAssign(next jrequests) tasks { +func (s *Server) checkAndAssign(next jmessages) tasks { var ts tasks for _, req := range next { s.log("Checking request for %q: %s", req.M, string(req.P)) @@ -232,6 +265,12 @@ func (s *Server) checkAndAssign(next jrequests) tasks { t.err = Errorf(code.InvalidRequest, "duplicate request id %q", id) } else if !s.versionOK(req.V) { t.err = ErrInvalidVersion + } else if !req.isRequestOrNotification() && s.call[id] != nil { + // This is a result or error for a pending push-call. + rsp := s.call[id] + delete(s.call, id) + rsp.ch <- req + continue // don't send a reply for this } else if req.M == "" { t.err = Errorf(code.InvalidRequest, "empty method name") } else if s.setContext(t, id) { @@ -317,37 +356,86 @@ func (s *Server) ServerInfo() *ServerInfo { return info } -// Push posts a server-side notification to the client. This is a non-standard -// extension of JSON-RPC, and may not be supported by all clients. Unless s -// was constructed with the AllowPush option set true, this method will always -// report an error (ErrNotifyUnsupported) without sending anything. If Push is -// called after the client connection is closed, it returns ErrConnClosed. -func (s *Server) Push(ctx context.Context, method string, params interface{}) error { +// Notify posts a single server-side notification to the client. +// +// This is a non-standard extension of JSON-RPC, and may not be supported by +// all clients. Unless s was constructed with the AllowPush option set true, +// this method will always report an error (ErrPushUnsupported) without sending +// anything. If Notify is called after the client connection is closed, it +// returns ErrConnClosed. +func (s *Server) Notify(ctx context.Context, method string, params interface{}) error { if !s.allowP { - return ErrNotifyUnsupported + return ErrPushUnsupported } + _, err := s.pushReq(ctx, false /* no ID */, method, params) + return err +} + +// Callback posts a single server-side call to the client. It blocks until a +// reply is received or the client connection terminates. A successful +// callback reports a nil error and a non-nil response. Errors returned by the +// client have concrete type *jrpc2.Error. +// +// This is a non-standard extension of JSON-RPC, and may not be supported by +// all clients. Unless s was constructed with the AllowPush option set true, +// this method will always report an error (ErrPushUnsupported) without sending +// anything. If Callback is called after the client connection is closed, it +// returns ErrConnClosed. +func (s *Server) Callback(ctx context.Context, method string, params interface{}) (*Response, error) { + if !s.allowP { + return nil, ErrPushUnsupported + } + rsp, err := s.pushReq(ctx, true /* set ID */, method, params) + if err != nil { + return nil, err + } + rsp.wait() + if err := rsp.Error(); err != nil { + return nil, filterError(err) + } + return rsp, nil +} + +func (s *Server) pushReq(ctx context.Context, wantID bool, method string, params interface{}) (rsp *Response, _ error) { var bits []byte if params != nil { v, err := json.Marshal(params) if err != nil { - return err + return nil, err } bits = v } s.mu.Lock() defer s.mu.Unlock() if s.ch == nil { - return ErrConnClosed + return nil, ErrConnClosed + } + + kind := "notification" + var jid json.RawMessage + if wantID { + kind = "call" + id := strconv.FormatInt(s.callID, 10) + s.callID++ + jid = json.RawMessage(id) + rsp = &Response{ + ch: make(chan *jmessage, 1), + id: id, + cancel: func() {}, + } + s.call[id] = rsp } - s.log("Posting server notification %q %s", method, string(bits)) - nw, err := encode(s.ch, jresponses{{ - V: Version, - M: method, - P: bits, + + s.log("Posting server %s %q %s", kind, method, string(bits)) + nw, err := encode(s.ch, jmessages{{ + V: Version, + ID: jid, + M: method, + P: bits, }}) s.metrics.CountAndSetMax("rpc.bytesWritten", int64(nw)) - s.metrics.Count("rpc.notifications", 1) - return err + s.metrics.Count("rpc."+kind+"s", 1) + return rsp, err } // Stop shuts down the server. It is safe to call this method multiple times or @@ -407,21 +495,23 @@ func (s *Server) stop(err error) { // Remove any pending requests from the queue, but retain notifications. // The server will process pending notifications before giving up. - for cur := s.inq.Front(); cur != nil; cur = cur.Next() { - var keep jrequests - for _, req := range cur.Value.(jrequests) { - if req.ID == nil { + // + // TODO(@creachadair): We need better tests for this behaviour. + var keep jmessages + for cur := s.inq.Front(); cur != nil; cur = s.inq.Front() { + for _, req := range cur.Value.(jmessages) { + if req.isNotification() { keep = append(keep, req) s.log("Retaining notification %p", req) } else { s.cancel(string(req.ID)) } } - if len(keep) != 0 { - s.inq.PushBack(keep) - } s.inq.Remove(cur) } + for _, elt := range keep { + s.inq.PushBack(jmessages{elt}) + } s.work.Broadcast() // Cancel any in-flight requests that made it out of the queue. @@ -447,7 +537,7 @@ func (s *Server) read(ch channel.Receiver) { for { // If the message is not sensible, report an error; otherwise enqueue it // for processing. Errors in individual requests are handled later. - var in jrequests + var in jmessages var derr error bits, err := ch.Recv() s.metrics.CountAndSetMax("rpc.bytesRead", int64(len(bits))) @@ -519,7 +609,7 @@ func (s *Server) pushError(err error) { jerr = &Error{code: code.FromError(err), message: err.Error()} } - nw, err := encode(s.ch, jresponses{{ + nw, err := encode(s.ch, jmessages{{ V: Version, ID: json.RawMessage("null"), E: jerr, @@ -564,8 +654,8 @@ type task struct { type tasks []*task -func (ts tasks) responses(rpcLog RPCLogger) jresponses { - var rsps jresponses +func (ts tasks) responses(rpcLog RPCLogger) jmessages { + var rsps jmessages for _, task := range ts { if task.hreq.id == nil { // Spec: "The Server MUST NOT reply to a Notification, including @@ -580,7 +670,7 @@ func (ts tasks) responses(rpcLog RPCLogger) jresponses { continue } } - rsp := &jresponse{V: Version, ID: task.hreq.id, batch: task.batch} + rsp := &jmessage{V: Version, ID: task.hreq.id, batch: task.batch} if rsp.ID == nil { rsp.ID = json.RawMessage("null") } @@ -602,3 +692,14 @@ func (ts tasks) responses(rpcLog RPCLogger) jresponses { } return rsps } + +// numValidNotifications reports the number of elements in ts that are +// syntactically valid notifications. +func (ts tasks) numValidNotifications() (n int) { + for _, t := range ts { + if t.err == nil && t.hreq.IsNotification() { + n++ + } + } + return +} diff --git a/vendor/github.com/google/go-cmp/cmp/compare.go b/vendor/github.com/google/go-cmp/cmp/compare.go index c9a63ceda..7ad400f29 100644 --- a/vendor/github.com/google/go-cmp/cmp/compare.go +++ b/vendor/github.com/google/go-cmp/cmp/compare.go @@ -6,6 +6,10 @@ // // This package is intended to be a more powerful and safer alternative to // reflect.DeepEqual for comparing whether two values are semantically equal. +// It is intended to only be used in tests, as performance is not a goal and +// it may panic if it cannot compare the values. Its propensity towards +// panicking means that its unsuitable for production environments where a +// spurious panic may be fatal. // // The primary features of cmp are: // diff --git a/vendor/github.com/google/go-cmp/cmp/report_compare.go b/vendor/github.com/google/go-cmp/cmp/report_compare.go index 17a05eede..d3fa154da 100644 --- a/vendor/github.com/google/go-cmp/cmp/report_compare.go +++ b/vendor/github.com/google/go-cmp/cmp/report_compare.go @@ -81,14 +81,19 @@ func (opts formatOptions) FormatDiff(v *valueNode) textNode { return opts.FormatDiffSlice(v) } + var withinSlice bool + if v.parent != nil && (v.parent.Type.Kind() == reflect.Slice || v.parent.Type.Kind() == reflect.Array) { + withinSlice = true + } + // For leaf nodes, format the value based on the reflect.Values alone. if v.MaxDepth == 0 { switch opts.DiffMode { case diffUnknown, diffIdentical: // Format Equal. if v.NumDiff == 0 { - outx := opts.FormatValue(v.ValueX, visitedPointers{}) - outy := opts.FormatValue(v.ValueY, visitedPointers{}) + outx := opts.FormatValue(v.ValueX, withinSlice, visitedPointers{}) + outy := opts.FormatValue(v.ValueY, withinSlice, visitedPointers{}) if v.NumIgnored > 0 && v.NumSame == 0 { return textEllipsis } else if outx.Len() < outy.Len() { @@ -101,8 +106,8 @@ func (opts formatOptions) FormatDiff(v *valueNode) textNode { // Format unequal. assert(opts.DiffMode == diffUnknown) var list textList - outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, visitedPointers{}) - outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, visitedPointers{}) + outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, withinSlice, visitedPointers{}) + outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, withinSlice, visitedPointers{}) if outx != nil { list = append(list, textRecord{Diff: '-', Value: outx}) } @@ -111,9 +116,9 @@ func (opts formatOptions) FormatDiff(v *valueNode) textNode { } return opts.WithTypeMode(emitType).FormatType(v.Type, list) case diffRemoved: - return opts.FormatValue(v.ValueX, visitedPointers{}) + return opts.FormatValue(v.ValueX, withinSlice, visitedPointers{}) case diffInserted: - return opts.FormatValue(v.ValueY, visitedPointers{}) + return opts.FormatValue(v.ValueY, withinSlice, visitedPointers{}) default: panic("invalid diff mode") } diff --git a/vendor/github.com/google/go-cmp/cmp/report_reflect.go b/vendor/github.com/google/go-cmp/cmp/report_reflect.go index 2761b6289..8f108833b 100644 --- a/vendor/github.com/google/go-cmp/cmp/report_reflect.go +++ b/vendor/github.com/google/go-cmp/cmp/report_reflect.go @@ -74,7 +74,7 @@ func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode { // FormatValue prints the reflect.Value, taking extra care to avoid descending // into pointers already in m. As pointers are visited, m is also updated. -func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out textNode) { +func (opts formatOptions) FormatValue(v reflect.Value, withinSlice bool, m visitedPointers) (out textNode) { if !v.IsValid() { return nil } @@ -108,12 +108,15 @@ func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out t return textLine(fmt.Sprint(v.Bool())) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return textLine(fmt.Sprint(v.Int())) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - // Unnamed uints are usually bytes or words, so use hexadecimal. - if t.PkgPath() == "" || t.Kind() == reflect.Uintptr { + case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return textLine(fmt.Sprint(v.Uint())) + case reflect.Uint8: + if withinSlice { return textLine(formatHex(v.Uint())) } return textLine(fmt.Sprint(v.Uint())) + case reflect.Uintptr: + return textLine(formatHex(v.Uint())) case reflect.Float32, reflect.Float64: return textLine(fmt.Sprint(v.Float())) case reflect.Complex64, reflect.Complex128: @@ -129,7 +132,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out t if value.IsZero(vv) { continue // Elide fields with zero values } - s := opts.WithTypeMode(autoType).FormatValue(vv, m) + s := opts.WithTypeMode(autoType).FormatValue(vv, false, m) list = append(list, textRecord{Key: t.Field(i).Name, Value: s}) } return textWrap{"{", list, "}"} @@ -156,7 +159,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out t continue } } - s := opts.WithTypeMode(elideType).FormatValue(vi, m) + s := opts.WithTypeMode(elideType).FormatValue(vi, true, m) list = append(list, textRecord{Value: s}) } return textWrap{ptr + "{", list, "}"} @@ -171,7 +174,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out t var list textList for _, k := range value.SortKeys(v.MapKeys()) { sk := formatMapKey(k) - sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), m) + sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), false, m) list = append(list, textRecord{Key: sk, Value: sv}) } if opts.PrintAddresses { @@ -189,7 +192,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out t ptr = formatPointer(v) } skipType = true // Let the underlying value print the type instead - return textWrap{"&" + ptr, opts.FormatValue(v.Elem(), m), ""} + return textWrap{"&" + ptr, opts.FormatValue(v.Elem(), false, m), ""} case reflect.Interface: if v.IsNil() { return textNil @@ -197,7 +200,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, m visitedPointers) (out t // Interfaces accept different concrete types, // so configure the underlying value to explicitly print the type. skipType = true // Print the concrete type instead - return opts.WithTypeMode(emitType).FormatValue(v.Elem(), m) + return opts.WithTypeMode(emitType).FormatValue(v.Elem(), false, m) default: panic(fmt.Sprintf("%v kind not handled", v.Kind())) } @@ -209,7 +212,7 @@ func formatMapKey(v reflect.Value) string { var opts formatOptions opts.TypeMode = elideType opts.ShallowPointers = true - s := opts.FormatValue(v, visitedPointers{}).String() + s := opts.FormatValue(v, false, visitedPointers{}).String() return strings.TrimSpace(s) } diff --git a/vendor/github.com/google/go-cmp/cmp/report_slices.go b/vendor/github.com/google/go-cmp/cmp/report_slices.go index eafcf2e4c..6f0847e63 100644 --- a/vendor/github.com/google/go-cmp/cmp/report_slices.go +++ b/vendor/github.com/google/go-cmp/cmp/report_slices.go @@ -172,7 +172,9 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { switch t.Elem().Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: ss = append(ss, fmt.Sprint(v.Index(i).Int())) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: + ss = append(ss, fmt.Sprint(v.Index(i).Uint())) + case reflect.Uint8, reflect.Uintptr: ss = append(ss, formatHex(v.Index(i).Uint())) case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: ss = append(ss, fmt.Sprint(v.Index(i).Interface())) diff --git a/vendor/golang.org/x/sync/semaphore/semaphore.go b/vendor/golang.org/x/sync/semaphore/semaphore.go index 7f096fef0..30f632c57 100644 --- a/vendor/golang.org/x/sync/semaphore/semaphore.go +++ b/vendor/golang.org/x/sync/semaphore/semaphore.go @@ -67,7 +67,12 @@ func (s *Weighted) Acquire(ctx context.Context, n int64) error { // fix up the queue, just pretend we didn't notice the cancelation. err = nil default: + isFront := s.waiters.Front() == elem s.waiters.Remove(elem) + // If we're at the front and there're extra tokens left, notify other waiters. + if isFront && s.size > s.cur { + s.notifyWaiters() + } } s.mu.Unlock() return err @@ -97,6 +102,11 @@ func (s *Weighted) Release(n int64) { s.mu.Unlock() panic("semaphore: released more than held") } + s.notifyWaiters() + s.mu.Unlock() +} + +func (s *Weighted) notifyWaiters() { for { next := s.waiters.Front() if next == nil { @@ -123,5 +133,4 @@ func (s *Weighted) Release(n int64) { s.waiters.Remove(next) close(w.ready) } - s.mu.Unlock() } diff --git a/vendor/modules.txt b/vendor/modules.txt index fde2d6d86..765284421 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -10,7 +10,7 @@ github.com/apparentlymart/go-textseg/v12/textseg github.com/armon/go-radix # github.com/bgentry/speakeasy v0.1.0 github.com/bgentry/speakeasy -# github.com/creachadair/jrpc2 v0.8.1 +# github.com/creachadair/jrpc2 v0.10.0 github.com/creachadair/jrpc2 github.com/creachadair/jrpc2/channel github.com/creachadair/jrpc2/code @@ -21,7 +21,7 @@ github.com/creachadair/jrpc2/server github.com/fatih/color # github.com/fsnotify/fsnotify v1.4.9 github.com/fsnotify/fsnotify -# github.com/google/go-cmp v0.4.0 +# github.com/google/go-cmp v0.4.1 github.com/google/go-cmp/cmp github.com/google/go-cmp/cmp/cmpopts github.com/google/go-cmp/cmp/internal/diff @@ -80,7 +80,7 @@ github.com/zclconf/go-cty/cty/json github.com/zclconf/go-cty/cty/set # golang.org/x/net v0.0.0-20191009170851-d66e71096ffb golang.org/x/net/idna -# golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e +# golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a golang.org/x/sync/semaphore # golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 golang.org/x/sys/unix From b31dccb42cc7893855afdcec57cdc0d9a3881a8f Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 5 Aug 2020 08:41:53 +0200 Subject: [PATCH 2/2] Adapt to new jrpc2 interface --- langserver/handlers/did_open.go | 4 ++-- langserver/handlers/initialize.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/langserver/handlers/did_open.go b/langserver/handlers/did_open.go index 0f0101fda..d7fd4029a 100644 --- a/langserver/handlers/did_open.go +++ b/langserver/handlers/did_open.go @@ -50,7 +50,7 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe // because we don't gather "init-able folders" in any way " You may need to run terraform init"+ " and reload your editor.", readableDir) - return jrpc2.ServerPush(ctx, "window/showMessage", lsp.ShowMessageParams{ + return jrpc2.PushNotify(ctx, "window/showMessage", lsp.ShowMessageParams{ Type: lsp.MTWarning, Message: msg, }) @@ -62,7 +62,7 @@ func (lh *logHandler) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpe " You can try setting paths to root modules explicitly in settings.", readableDir, candidatePaths(rootDir, candidates[1:]), candidateDir) - return jrpc2.ServerPush(ctx, "window/showMessage", lsp.ShowMessageParams{ + return jrpc2.PushNotify(ctx, "window/showMessage", lsp.ShowMessageParams{ Type: lsp.MTWarning, Message: msg, }) diff --git a/langserver/handlers/initialize.go b/langserver/handlers/initialize.go index 3824510d9..c0d7764f4 100644 --- a/langserver/handlers/initialize.go +++ b/langserver/handlers/initialize.go @@ -74,7 +74,7 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam return serverCaps, err } if len(out.UnusedKeys) > 0 { - jrpc2.ServerPush(ctx, "window/showMessage", &lsp.ShowMessageParams{ + jrpc2.PushNotify(ctx, "window/showMessage", &lsp.ShowMessageParams{ Type: lsp.MTWarning, Message: fmt.Sprintf("Unknown configuration options: %q", out.UnusedKeys), }) @@ -87,7 +87,7 @@ func (lh *logHandler) Initialize(ctx context.Context, params lsp.InitializeParam for _, rawPath := range cfgOpts.RootModulePaths { rmPath, err := resolvePath(rootDir, rawPath) if err != nil { - jrpc2.ServerPush(ctx, "window/showMessage", &lsp.ShowMessageParams{ + jrpc2.PushNotify(ctx, "window/showMessage", &lsp.ShowMessageParams{ Type: lsp.MTWarning, Message: fmt.Sprintf("Ignoring root module path %s: %s", rawPath, err), })