Skip to content

Commit 619de4e

Browse files
committed
internal/jsonrpc2: refactor to enable a more advanced request
This separates hides the wire structures, and then exposes a new Request type to allow for it to carry advanced features. It also embeds the connection into the request and changes the signature of the handler to no longer require a separate Conn argument. Change-Id: I20b54f146285f7a9cb5f279c6ebdf0f286f4b829 Reviewed-on: https://go-review.googlesource.com/c/tools/+/183717 Run-TryBot: Ian Cottrell <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]>
1 parent a6ef77d commit 619de4e

File tree

6 files changed

+162
-147
lines changed

6 files changed

+162
-147
lines changed

internal/jsonrpc2/jsonrpc2.go

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,27 @@ type Conn struct {
3333
stream Stream
3434
err error
3535
pendingMu sync.Mutex // protects the pending map
36-
pending map[ID]chan *Response
36+
pending map[ID]chan *wireResponse
3737
handlingMu sync.Mutex // protects the handling map
3838
handling map[ID]handling
3939
}
4040

41+
// Request is sent to a server to represent a Call or Notify operaton.
42+
type Request struct {
43+
conn *Conn
44+
45+
// Method is a string containing the method name to invoke.
46+
Method string
47+
// Params is either a struct or an array with the parameters of the method.
48+
Params *json.RawMessage
49+
// The id of this request, used to tie the response back to the request.
50+
// Will be either a string or a number. If not set, the request is a notify,
51+
// and no response is possible.
52+
ID *ID
53+
}
54+
4155
type queueEntry struct {
4256
ctx context.Context
43-
c *Conn
4457
r *Request
4558
size int64
4659
}
@@ -50,16 +63,14 @@ type queueEntry struct {
5063
// call Reply on the Conn with the supplied request.
5164
// Handlers are called synchronously, they should pass the work off to a go
5265
// routine if they are going to take a long time.
53-
type Handler func(context.Context, *Conn, *Request)
66+
type Handler func(context.Context, *Request)
5467

5568
// Canceler is an option you can pass to NewConn which is invoked for
5669
// cancelled outgoing requests.
57-
// The request will have the ID filled in, which can be used to propagate the
58-
// cancel to the other process if needed.
5970
// It is okay to use the connection to send notifications, but the context will
6071
// be in the cancelled state, so you must do it with the background context
6172
// instead.
62-
type Canceler func(context.Context, *Conn, *Request)
73+
type Canceler func(context.Context, *Conn, ID)
6374

6475
type rpcStats struct {
6576
server bool
@@ -133,17 +144,17 @@ func NewErrorf(code int64, format string, args ...interface{}) *Error {
133144
func NewConn(s Stream) *Conn {
134145
conn := &Conn{
135146
stream: s,
136-
pending: make(map[ID]chan *Response),
147+
pending: make(map[ID]chan *wireResponse),
137148
handling: make(map[ID]handling),
138149
}
139150
// the default handler reports a method error
140-
conn.Handler = func(ctx context.Context, c *Conn, r *Request) {
151+
conn.Handler = func(ctx context.Context, r *Request) {
141152
if r.IsNotify() {
142-
c.Reply(ctx, r, nil, NewErrorf(CodeMethodNotFound, "method %q not found", r.Method))
153+
r.Reply(ctx, nil, NewErrorf(CodeMethodNotFound, "method %q not found", r.Method))
143154
}
144155
}
145-
// the default canceller does nothing
146-
conn.Canceler = func(context.Context, *Conn, *Request) {}
156+
// the default canceler does nothing
157+
conn.Canceler = func(context.Context, *Conn, ID) {}
147158
// the default logger does nothing
148159
conn.Logger = func(Direction, *ID, time.Duration, string, *json.RawMessage, *Error) {}
149160
return conn
@@ -174,7 +185,7 @@ func (c *Conn) Notify(ctx context.Context, method string, params interface{}) (e
174185
if err != nil {
175186
return fmt.Errorf("marshalling notify parameters: %v", err)
176187
}
177-
request := &Request{
188+
request := &wireRequest{
178189
Method: method,
179190
Params: jsonParams,
180191
}
@@ -200,7 +211,7 @@ func (c *Conn) Call(ctx context.Context, method string, params, result interface
200211
if err != nil {
201212
return fmt.Errorf("marshalling call parameters: %v", err)
202213
}
203-
request := &Request{
214+
request := &wireRequest{
204215
ID: &id,
205216
Method: method,
206217
Params: jsonParams,
@@ -212,7 +223,7 @@ func (c *Conn) Call(ctx context.Context, method string, params, result interface
212223
}
213224
// we have to add ourselves to the pending map before we send, otherwise we
214225
// are racing the response
215-
rchan := make(chan *Response)
226+
rchan := make(chan *wireResponse)
216227
c.pendingMu.Lock()
217228
c.pending[id] = rchan
218229
c.pendingMu.Unlock()
@@ -249,40 +260,48 @@ func (c *Conn) Call(ctx context.Context, method string, params, result interface
249260
return nil
250261
case <-ctx.Done():
251262
// allow the handler to propagate the cancel
252-
c.Canceler(ctx, c, request)
263+
c.Canceler(ctx, c, id)
253264
return ctx.Err()
254265
}
255266
}
256267

268+
// Conn returns the connection that created this request.
269+
func (r *Request) Conn() *Conn { return r.conn }
270+
271+
// IsNotify returns true if this request is a notification.
272+
func (r *Request) IsNotify() bool {
273+
return r.ID == nil
274+
}
275+
257276
// Reply sends a reply to the given request.
258277
// It is an error to call this if request was not a call.
259278
// You must call this exactly once for any given request.
260279
// If err is set then result will be ignored.
261-
func (c *Conn) Reply(ctx context.Context, req *Request, result interface{}, err error) error {
262-
ctx, st := trace.StartSpan(ctx, req.Method+":reply", trace.WithSpanKind(trace.SpanKindClient))
280+
func (r *Request) Reply(ctx context.Context, result interface{}, err error) error {
281+
ctx, st := trace.StartSpan(ctx, r.Method+":reply", trace.WithSpanKind(trace.SpanKindClient))
263282
defer st.End()
264283

265-
if req.IsNotify() {
284+
if r.IsNotify() {
266285
return fmt.Errorf("reply not invoked with a valid call")
267286
}
268-
c.handlingMu.Lock()
269-
handling, found := c.handling[*req.ID]
287+
r.conn.handlingMu.Lock()
288+
handling, found := r.conn.handling[*r.ID]
270289
if found {
271-
delete(c.handling, *req.ID)
290+
delete(r.conn.handling, *r.ID)
272291
}
273-
c.handlingMu.Unlock()
292+
r.conn.handlingMu.Unlock()
274293
if !found {
275-
return fmt.Errorf("not a call in progress: %v", req.ID)
294+
return fmt.Errorf("not a call in progress: %v", r.ID)
276295
}
277296

278297
elapsed := time.Since(handling.start)
279298
var raw *json.RawMessage
280299
if err == nil {
281300
raw, err = marshalToRaw(result)
282301
}
283-
response := &Response{
302+
response := &wireResponse{
284303
Result: raw,
285-
ID: req.ID,
304+
ID: r.ID,
286305
}
287306
if err != nil {
288307
if callErr, ok := err.(*Error); ok {
@@ -295,8 +314,8 @@ func (c *Conn) Reply(ctx context.Context, req *Request, result interface{}, err
295314
if err != nil {
296315
return err
297316
}
298-
c.Logger(Send, response.ID, elapsed, req.Method, response.Result, response.Error)
299-
n, err := c.stream.Write(ctx, data)
317+
r.conn.Logger(Send, response.ID, elapsed, r.Method, response.Result, response.Error)
318+
n, err := r.conn.stream.Write(ctx, data)
300319

301320
v := ctx.Value(rpcStatsKey)
302321
if v != nil {
@@ -332,7 +351,7 @@ type combined struct {
332351
}
333352

334353
func (c *Conn) deliver(ctx context.Context, q chan queueEntry, request *Request, size int64) bool {
335-
e := queueEntry{ctx: ctx, c: c, r: request, size: size}
354+
e := queueEntry{ctx: ctx, r: request, size: size}
336355
if !c.RejectIfOverloaded {
337356
q <- e
338357
return true
@@ -361,7 +380,7 @@ func (c *Conn) Run(ctx context.Context) error {
361380
}
362381
ctx, rpcStats := start(ctx, true, e.r.Method, e.r.ID)
363382
rpcStats.received += e.size
364-
c.Handler(ctx, e.c, e.r)
383+
c.Handler(ctx, e.r)
365384
rpcStats.end(ctx, nil)
366385
}
367386
}()
@@ -385,6 +404,7 @@ func (c *Conn) Run(ctx context.Context) error {
385404
case msg.Method != "":
386405
// if method is set it must be a request
387406
request := &Request{
407+
conn: c,
388408
Method: msg.Method,
389409
Params: msg.Params,
390410
ID: msg.ID,
@@ -407,7 +427,7 @@ func (c *Conn) Run(ctx context.Context) error {
407427
c.Logger(Receive, request.ID, -1, request.Method, request.Params, nil)
408428
if !c.deliver(reqCtx, q, request, n) {
409429
// queue is full, reject the message by directly replying
410-
c.Reply(ctx, request, nil, NewErrorf(CodeServerOverloaded, "no room in queue"))
430+
request.Reply(ctx, nil, NewErrorf(CodeServerOverloaded, "no room in queue"))
411431
}
412432
}
413433
case msg.ID != nil:
@@ -419,7 +439,7 @@ func (c *Conn) Run(ctx context.Context) error {
419439
}
420440
c.pendingMu.Unlock()
421441
// and send the reply to the channel
422-
response := &Response{
442+
response := &wireResponse{
423443
Result: msg.Result,
424444
Error: msg.Error,
425445
ID: msg.ID,

internal/jsonrpc2/jsonrpc2_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,36 +122,36 @@ func run(ctx context.Context, t *testing.T, withHeaders bool, r io.ReadCloser, w
122122
return conn
123123
}
124124

125-
func handle(ctx context.Context, c *jsonrpc2.Conn, r *jsonrpc2.Request) {
125+
func handle(ctx context.Context, r *jsonrpc2.Request) {
126126
switch r.Method {
127127
case "no_args":
128128
if r.Params != nil {
129-
c.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
129+
r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidParams, "Expected no params"))
130130
return
131131
}
132-
c.Reply(ctx, r, true, nil)
132+
r.Reply(ctx, true, nil)
133133
case "one_string":
134134
var v string
135135
if err := json.Unmarshal(*r.Params, &v); err != nil {
136-
c.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
136+
r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
137137
return
138138
}
139-
c.Reply(ctx, r, "got:"+v, nil)
139+
r.Reply(ctx, "got:"+v, nil)
140140
case "one_number":
141141
var v int
142142
if err := json.Unmarshal(*r.Params, &v); err != nil {
143-
c.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
143+
r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
144144
return
145145
}
146-
c.Reply(ctx, r, fmt.Sprintf("got:%d", v), nil)
146+
r.Reply(ctx, fmt.Sprintf("got:%d", v), nil)
147147
case "join":
148148
var v []string
149149
if err := json.Unmarshal(*r.Params, &v); err != nil {
150-
c.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
150+
r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err.Error()))
151151
return
152152
}
153-
c.Reply(ctx, r, path.Join(v...), nil)
153+
r.Reply(ctx, path.Join(v...), nil)
154154
default:
155-
c.Reply(ctx, r, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not found", r.Method))
155+
r.Reply(ctx, nil, jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not found", r.Method))
156156
}
157157
}

internal/jsonrpc2/wire.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ const (
3434
CodeServerOverloaded = -32000
3535
)
3636

37-
// Request is sent to a server to represent a Call or Notify operaton.
38-
type Request struct {
37+
// wireRequest is sent to a server to represent a Call or Notify operaton.
38+
type wireRequest struct {
3939
// VersionTag is always encoded as the string "2.0"
4040
VersionTag VersionTag `json:"jsonrpc"`
4141
// Method is a string containing the method name to invoke.
@@ -48,11 +48,11 @@ type Request struct {
4848
ID *ID `json:"id,omitempty"`
4949
}
5050

51-
// Response is a reply to a Request.
51+
// wireResponse is a reply to a Request.
5252
// It will always have the ID field set to tie it back to a request, and will
5353
// have either the Result or Error fields set depending on whether it is a
5454
// success or failure response.
55-
type Response struct {
55+
type wireResponse struct {
5656
// VersionTag is always encoded as the string "2.0"
5757
VersionTag VersionTag `json:"jsonrpc"`
5858
// Result is the response value, and is required on success.
@@ -87,11 +87,6 @@ type ID struct {
8787
Number int64
8888
}
8989

90-
// IsNotify returns true if this request is a notification.
91-
func (r *Request) IsNotify() bool {
92-
return r.ID == nil
93-
}
94-
9590
func (err *Error) Error() string {
9691
if err == nil {
9792
return ""

internal/lsp/protocol/protocol.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import (
1414
const defaultMessageBufferSize = 20
1515
const defaultRejectIfOverloaded = false
1616

17-
func canceller(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) {
18-
conn.Notify(context.Background(), "$/cancelRequest", &CancelParams{ID: *req.ID})
17+
func canceller(ctx context.Context, conn *jsonrpc2.Conn, id jsonrpc2.ID) {
18+
conn.Notify(context.Background(), "$/cancelRequest", &CancelParams{ID: id})
1919
}
2020

2121
func NewClient(stream jsonrpc2.Stream, client Client) (*jsonrpc2.Conn, Server, xlog.Logger) {
@@ -39,11 +39,11 @@ func NewServer(stream jsonrpc2.Stream, server Server) (*jsonrpc2.Conn, Client, x
3939
return conn, client, log
4040
}
4141

42-
func sendParseError(ctx context.Context, log xlog.Logger, conn *jsonrpc2.Conn, req *jsonrpc2.Request, err error) {
42+
func sendParseError(ctx context.Context, log xlog.Logger, req *jsonrpc2.Request, err error) {
4343
if _, ok := err.(*jsonrpc2.Error); !ok {
4444
err = jsonrpc2.NewErrorf(jsonrpc2.CodeParseError, "%v", err)
4545
}
46-
if err := conn.Reply(ctx, req, nil, err); err != nil {
46+
if err := req.Reply(ctx, nil, err); err != nil {
4747
log.Errorf(ctx, "%v", err)
4848
}
4949
}

0 commit comments

Comments
 (0)