internal/jsonrpc2_v2: close the underlying connection if Wait is called instead of Close
(*Server).run calls Wait on all of its connections before returning, but does not call Close explicitly. If Close is necessary to release resources (as is often the case for a net.Conn), the server ends up leaking resources. For golang/go#46047 Change-Id: I4ad048c4f5e5d3f14f992eee6388acd42398c26b Reviewed-on: https://go-review.googlesource.com/c/tools/+/388599 TryBot-Result: Gopher Robot <gobot@golang.org> gopls-CI: kokoro <noreply+kokoro@google.com> Run-TryBot: Bryan Mills <bcmills@google.com> Auto-Submit: Bryan Mills <bcmills@google.com> Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
parent
d6f81f39d9
commit
dd9a221959
1 changed files with 32 additions and 11 deletions
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"golang.org/x/tools/internal/event"
|
"golang.org/x/tools/internal/event"
|
||||||
|
|
@ -45,8 +46,11 @@ type ConnectionOptions struct {
|
||||||
// Connection is bidirectional; it does not have a designated server or client
|
// Connection is bidirectional; it does not have a designated server or client
|
||||||
// end.
|
// end.
|
||||||
type Connection struct {
|
type Connection struct {
|
||||||
seq int64 // must only be accessed using atomic operations
|
seq int64 // must only be accessed using atomic operations
|
||||||
closer io.Closer
|
|
||||||
|
closeOnce sync.Once
|
||||||
|
closer io.Closer
|
||||||
|
|
||||||
writerBox chan Writer
|
writerBox chan Writer
|
||||||
outgoingBox chan map[ID]chan<- *Response
|
outgoingBox chan map[ID]chan<- *Response
|
||||||
incomingBox chan map[ID]*incoming
|
incomingBox chan map[ID]*incoming
|
||||||
|
|
@ -275,14 +279,13 @@ func (c *Connection) Wait() error {
|
||||||
// This does not cancel in flight requests, but waits for them to gracefully complete.
|
// This does not cancel in flight requests, but waits for them to gracefully complete.
|
||||||
func (c *Connection) Close() error {
|
func (c *Connection) Close() error {
|
||||||
// close the underlying stream
|
// close the underlying stream
|
||||||
if err := c.closer.Close(); err != nil && !isClosingError(err) {
|
c.closeOnce.Do(func() {
|
||||||
return err
|
if err := c.closer.Close(); err != nil {
|
||||||
}
|
c.async.setError(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
// and then wait for it to cause the connection to close
|
// and then wait for it to cause the connection to close
|
||||||
if err := c.Wait(); err != nil && !isClosingError(err) {
|
return c.Wait()
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// readIncoming collects inbound messages from the reader and delivers them, either responding
|
// readIncoming collects inbound messages from the reader and delivers them, either responding
|
||||||
|
|
@ -295,7 +298,9 @@ func (c *Connection) readIncoming(ctx context.Context, reader Reader, toQueue ch
|
||||||
msg, n, err := reader.Read(ctx)
|
msg, n, err := reader.Read(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The stream failed, we cannot continue
|
// The stream failed, we cannot continue
|
||||||
c.async.setError(err)
|
if !isClosingError(err) {
|
||||||
|
c.async.setError(err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
|
|
@ -395,7 +400,23 @@ func (c *Connection) manageQueue(ctx context.Context, preempter Preempter, fromR
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Connection) deliverMessages(ctx context.Context, handler Handler, fromQueue <-chan *incoming) {
|
func (c *Connection) deliverMessages(ctx context.Context, handler Handler, fromQueue <-chan *incoming) {
|
||||||
defer c.async.done()
|
defer func() {
|
||||||
|
// Close the underlying ReadWriteCloser if not already closed. We're about
|
||||||
|
// to mark the Connection as done, so we'd better actually be done! 😅
|
||||||
|
//
|
||||||
|
// TODO(bcmills): This is actually a bit premature, since we may have
|
||||||
|
// asynchronous handlers still in flight at this point, but it's at least no
|
||||||
|
// more premature than calling c.async.done at this point (which we were
|
||||||
|
// already doing). This will get a proper fix in https://go.dev/cl/388134.
|
||||||
|
c.closeOnce.Do(func() {
|
||||||
|
if err := c.closer.Close(); err != nil {
|
||||||
|
c.async.setError(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
c.async.done()
|
||||||
|
}()
|
||||||
|
|
||||||
for entry := range fromQueue {
|
for entry := range fromQueue {
|
||||||
// cancel any messages in the queue that we have a pending cancel for
|
// cancel any messages in the queue that we have a pending cancel for
|
||||||
var result interface{}
|
var result interface{}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue