internal/jsonrpc2_v2: eliminate a temporary connection leak in (*Server).run
Prior to this CL, (*Server).run only filters out inactive connections when it accepts a new connection. If existing connections complete, their associated resources can't be garbage-collected until either the next connection is accepted or the Listener is closed. This change moves the open-connection accounting to an explicit hook passed to newConnection, eliminating the need to call Wait entirely. For golang/go#46047 Change-Id: I3732cb463fcea0c142f17f2b1510fdfd2dbc81da Reviewed-on: https://go-review.googlesource.com/c/tools/+/388774 Auto-Submit: Bryan Mills <bcmills@google.com> Reviewed-by: Ian Cottrell <iancottrell@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> Run-TryBot: Bryan Mills <bcmills@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
parent
1afb0eb5ac
commit
a7abf540a7
2 changed files with 22 additions and 31 deletions
|
|
@ -74,6 +74,7 @@ type Connection struct {
|
||||||
handler Handler
|
handler Handler
|
||||||
|
|
||||||
onInternalError func(error)
|
onInternalError func(error)
|
||||||
|
onDone func()
|
||||||
}
|
}
|
||||||
|
|
||||||
// inFlightState records the state of the incoming and outgoing calls on a
|
// inFlightState records the state of the incoming and outgoing calls on a
|
||||||
|
|
@ -113,6 +114,9 @@ func (c *Connection) updateInFlight(f func(*inFlightState)) {
|
||||||
idle := s.incoming == 0 && len(s.outgoing) == 0 && !s.handlerRunning
|
idle := s.incoming == 0 && len(s.outgoing) == 0 && !s.handlerRunning
|
||||||
if idle && (s.closing || s.readErr != nil) && !s.closed {
|
if idle && (s.closing || s.readErr != nil) && !s.closed {
|
||||||
c.closeErr <- c.closer.Close()
|
c.closeErr <- c.closer.Close()
|
||||||
|
if c.onDone != nil {
|
||||||
|
c.onDone()
|
||||||
|
}
|
||||||
s.closed = true
|
s.closed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -131,8 +135,13 @@ func (o ConnectionOptions) Bind(context.Context, *Connection) (ConnectionOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// newConnection creates a new connection and runs it.
|
// newConnection creates a new connection and runs it.
|
||||||
|
//
|
||||||
// This is used by the Dial and Serve functions to build the actual connection.
|
// This is used by the Dial and Serve functions to build the actual connection.
|
||||||
func newConnection(bindCtx context.Context, rwc io.ReadWriteCloser, binder Binder) (*Connection, error) {
|
//
|
||||||
|
// The connection is closed automatically (and its resources cleaned up) when
|
||||||
|
// the last request has completed after the underlying ReadWriteCloser breaks,
|
||||||
|
// but it may be stopped earlier by calling Close (for a clean shutdown).
|
||||||
|
func newConnection(bindCtx context.Context, rwc io.ReadWriteCloser, binder Binder, onDone func()) (*Connection, error) {
|
||||||
// TODO: Should we create a new event span here?
|
// TODO: Should we create a new event span here?
|
||||||
// This will propagate cancellation from ctx; should it?
|
// This will propagate cancellation from ctx; should it?
|
||||||
ctx := notDone{bindCtx}
|
ctx := notDone{bindCtx}
|
||||||
|
|
@ -141,6 +150,7 @@ func newConnection(bindCtx context.Context, rwc io.ReadWriteCloser, binder Binde
|
||||||
closer: rwc,
|
closer: rwc,
|
||||||
closeErr: make(chan error, 1),
|
closeErr: make(chan error, 1),
|
||||||
writer: make(chan Writer, 1),
|
writer: make(chan Writer, 1),
|
||||||
|
onDone: onDone,
|
||||||
}
|
}
|
||||||
|
|
||||||
options, err := binder.Bind(bindCtx, c)
|
options, err := binder.Bind(bindCtx, c)
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ func Dial(ctx context.Context, dialer Dialer, binder Binder) (*Connection, error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return newConnection(ctx, rwc, binder)
|
return newConnection(ctx, rwc, binder, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve starts a new server listening for incoming connections and returns
|
// Serve starts a new server listening for incoming connections and returns
|
||||||
|
|
@ -84,25 +84,25 @@ func (s *Server) Wait() error {
|
||||||
// duration, otherwise it exits only on error.
|
// duration, otherwise it exits only on error.
|
||||||
func (s *Server) run(ctx context.Context) {
|
func (s *Server) run(ctx context.Context) {
|
||||||
defer s.async.done()
|
defer s.async.done()
|
||||||
var activeConns []*Connection
|
|
||||||
|
var activeConns sync.WaitGroup
|
||||||
for {
|
for {
|
||||||
// we never close the accepted connection, we rely on the other end
|
// We never close the accepted connection — we rely on the other end
|
||||||
// closing or the socket closing itself naturally
|
// closing or the socket closing itself naturally.
|
||||||
rwc, err := s.listener.Accept(ctx)
|
rwc, err := s.listener.Accept(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !isClosingError(err) {
|
if !isClosingError(err) {
|
||||||
s.async.setError(err)
|
s.async.setError(err)
|
||||||
}
|
}
|
||||||
// we are done generating new connections for good
|
// We are done generating new connections for good.
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// see if any connections were closed while we were waiting
|
// A new inbound connection.
|
||||||
activeConns = onlyActive(activeConns)
|
activeConns.Add(1)
|
||||||
|
_, err = newConnection(ctx, rwc, s.binder, activeConns.Done)
|
||||||
// a new inbound connection,
|
|
||||||
conn, err := newConnection(ctx, rwc, s.binder)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
activeConns.Done()
|
||||||
if !isClosingError(err) {
|
if !isClosingError(err) {
|
||||||
s.async.setError(err)
|
s.async.setError(err)
|
||||||
s.listener.Close()
|
s.listener.Close()
|
||||||
|
|
@ -110,27 +110,8 @@ func (s *Server) run(ctx context.Context) {
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
activeConns = append(activeConns, conn)
|
|
||||||
}
|
}
|
||||||
|
activeConns.Wait()
|
||||||
// wait for all active conns to finish
|
|
||||||
for _, c := range activeConns {
|
|
||||||
c.Wait()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func onlyActive(conns []*Connection) []*Connection {
|
|
||||||
i := 0
|
|
||||||
for _, c := range conns {
|
|
||||||
c.updateInFlight(func(s *inFlightState) {
|
|
||||||
if !s.closed {
|
|
||||||
conns[i] = c
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// trim the slice down
|
|
||||||
return conns[:i]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// isClosingError reports if the error occurs normally during the process of
|
// isClosingError reports if the error occurs normally during the process of
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue