Skip to content

Commit d41a43b

Browse files
author
Bryan C. Mills
committed
internal/jsonrpc2_v2: fix a potential deadlock when (*Conn).Close is invoked during Bind
This fixes the goroutine leak reported in https://build.golang.org/log/ae36d36843ca240e9e080886417a8798dd4c9618. Fixes golang/go#46047 (hopefully for real this time). Change-Id: I360e54d819849a35284c61d3a0655cc175d81f77 Reviewed-on: https://go-review.googlesource.com/c/tools/+/448095 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Robert Findley <[email protected]> Run-TryBot: Bryan Mills <[email protected]> Reviewed-by: Alan Donovan <[email protected]> gopls-CI: kokoro <[email protected]>
1 parent 3057465 commit d41a43b

File tree

1 file changed

+14
-5
lines changed

1 file changed

+14
-5
lines changed

internal/jsonrpc2_v2/conn.go

+14-5
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ type Connection struct {
8282
// Connection.
8383
type inFlightState struct {
8484
connClosing bool // true when the Connection's Close method has been called
85+
reading bool // true while the readIncoming goroutine is running
8586
readErr error // non-nil when the readIncoming goroutine exits (typically io.EOF)
8687
writeErr error // non-nil if a call to the Writer has failed with a non-canceled Context
8788

@@ -140,14 +141,13 @@ func (c *Connection) updateInFlight(f func(*inFlightState)) {
140141
s.closeErr = s.closer.Close()
141142
s.closer = nil // prevent duplicate Close calls
142143
}
143-
if s.readErr == nil {
144+
if s.reading {
144145
// The readIncoming goroutine is still running. Our call to Close should
145146
// cause it to exit soon, at which point it will make another call to
146-
// updateInFlight, set s.readErr to a non-nil error, and mark the
147-
// Connection done.
147+
// updateInFlight, set s.reading to false, and mark the Connection done.
148148
} else {
149-
// The readIncoming goroutine has exited. Since everything else is idle,
150-
// we're completely done.
149+
// The readIncoming goroutine has exited, or never started to begin with.
150+
// Since everything else is idle, we're completely done.
151151
if c.onDone != nil {
152152
c.onDone()
153153
}
@@ -240,10 +240,18 @@ func newConnection(bindCtx context.Context, rwc io.ReadWriteCloser, binder Binde
240240
reader := framer.Reader(rwc)
241241

242242
c.updateInFlight(func(s *inFlightState) {
243+
select {
244+
case <-c.done:
245+
// Bind already closed the connection; don't start a goroutine to read it.
246+
return
247+
default:
248+
}
249+
243250
// The goroutine started here will continue until the underlying stream is closed.
244251
//
245252
// (If the Binder closed the Connection already, this should error out and
246253
// return almost immediately.)
254+
s.reading = true
247255
go c.readIncoming(ctx, reader, options.Preempter)
248256
})
249257
return c
@@ -514,6 +522,7 @@ func (c *Connection) readIncoming(ctx context.Context, reader Reader, preempter
514522
}
515523

516524
c.updateInFlight(func(s *inFlightState) {
525+
s.reading = false
517526
s.readErr = err
518527

519528
// Retire any outgoing requests that were still in flight: with the Reader no

0 commit comments

Comments
 (0)