Skip to content

Commit fd32990

Browse files
Bryan C. Millsgopherbot
Bryan C. Mills
authored andcommitted
internal/jsonrpc2_v2: error out in-flight client calls when the reader breaks
Otherwise, the Await method on the corresponding AsyncCall will never unblock, leading to a deadlock (detected by the test changes in CL 388597). For golang/go#46047 For golang/go#46520 For golang/go#49387 Change-Id: I7954f80786059772ddd7f8c98d8752d56d074919 Reviewed-on: https://go-review.googlesource.com/c/tools/+/388775 Run-TryBot: Bryan Mills <bcmills@google.com> Reviewed-by: Ian Cottrell <iancottrell@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> Auto-Submit: Bryan Mills <bcmills@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
1 parent 0e222f5 commit fd32990

File tree

2 files changed

+42
-11
lines changed

2 files changed

+42
-11
lines changed

Diff for: internal/jsonrpc2_v2/conn.go

+38-11
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,25 @@ func (c *Connection) Call(ctx context.Context, method string, params interface{}
175175
// are racing the response.
176176
// rchan is buffered in case the response arrives without a listener.
177177
result.response = make(chan *Response, 1)
178-
pending := <-c.outgoingBox
179-
pending[result.id] = result.response
180-
c.outgoingBox <- pending
178+
outgoing, ok := <-c.outgoingBox
179+
if !ok {
180+
// If the call failed due to (say) an I/O error or broken pipe, attribute it
181+
// as such. (If the error is nil, then the connection must have been shut
182+
// down cleanly.)
183+
err := c.async.wait()
184+
if err == nil {
185+
err = ErrClientClosing
186+
}
187+
188+
resp, respErr := NewResponse(result.id, nil, err)
189+
if respErr != nil {
190+
panic(fmt.Errorf("unexpected error from NewResponse: %w", respErr))
191+
}
192+
result.response <- resp
193+
return result
194+
}
195+
outgoing[result.id] = result.response
196+
c.outgoingBox <- outgoing
181197
// now we are ready to send
182198
if err := c.write(ctx, call); err != nil {
183199
// sending failed, we will never get a response, so deliver a fake one
@@ -290,8 +306,19 @@ func (c *Connection) Close() error {
290306

291307
// readIncoming collects inbound messages from the reader and delivers them, either responding
292308
// to outgoing calls or feeding requests to the queue.
293-
func (c *Connection) readIncoming(ctx context.Context, reader Reader, toQueue chan<- *incoming) {
294-
defer close(toQueue)
309+
func (c *Connection) readIncoming(ctx context.Context, reader Reader, toQueue chan<- *incoming) (err error) {
310+
defer func() {
311+
// Retire any outgoing requests that were still in flight.
312+
// With the Reader no longer being processed, they necessarily cannot receive a response.
313+
outgoing := <-c.outgoingBox
314+
close(c.outgoingBox) // Prevent new outgoing requests, which would deadlock.
315+
for id, responseBox := range outgoing {
316+
responseBox <- &Response{ID: id, Error: err}
317+
}
318+
319+
close(toQueue)
320+
}()
321+
295322
for {
296323
// get the next message
297324
// no lock is needed, this is the only reader
@@ -301,7 +328,7 @@ func (c *Connection) readIncoming(ctx context.Context, reader Reader, toQueue ch
301328
if !isClosingError(err) {
302329
c.async.setError(err)
303330
}
304-
return
331+
return err
305332
}
306333
switch msg := msg.(type) {
307334
case *Request:
@@ -340,12 +367,12 @@ func (c *Connection) readIncoming(ctx context.Context, reader Reader, toQueue ch
340367
}
341368

342369
func (c *Connection) incomingResponse(msg *Response) {
343-
pending := <-c.outgoingBox
344-
response, ok := pending[msg.ID]
345-
if ok {
346-
delete(pending, msg.ID)
370+
var response chan<- *Response
371+
if outgoing, ok := <-c.outgoingBox; ok {
372+
response = outgoing[msg.ID]
373+
delete(outgoing, msg.ID)
374+
c.outgoingBox <- outgoing
347375
}
348-
c.outgoingBox <- pending
349376
if response != nil {
350377
response <- msg
351378
}

Diff for: internal/jsonrpc2_v2/wire.go

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ var (
3333
ErrServerOverloaded = NewError(-32000, "JSON RPC overloaded")
3434
// ErrUnknown should be used for all non coded errors.
3535
ErrUnknown = NewError(-32001, "JSON RPC unknown error")
36+
// ErrServerClosing is returned for calls that arrive while the server is closing.
37+
ErrServerClosing = NewError(-32002, "JSON RPC server is closing")
38+
// ErrClientClosing is a dummy error returned for calls initiated while the client is closing.
39+
ErrClientClosing = NewError(-32003, "JSON RPC client is closing")
3640
)
3741

3842
const wireVersion = "2.0"

0 commit comments

Comments
 (0)