aboutsummaryrefslogtreecommitdiff
path: root/internal/jsonrpc2/serve.go
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-03-30 16:55:43 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-03-30 16:55:43 +0000
commit3225eca48f7ce16eb31b2dd5a170806c1214a49e (patch)
tree7e04d345c214f3efac3c4b86c7ec3e831c500437 /internal/jsonrpc2/serve.go
parent457aa020f87e3a763226dc76aa3459fd23d0aa11 (diff)
parentf10932f763d058b0dcb3acfb795c869996fef47b (diff)
downloadgolang-x-tools-3225eca48f7ce16eb31b2dd5a170806c1214a49e.tar.gz
Snap for 8383211 from f10932f763d058b0dcb3acfb795c869996fef47b to build-tools-release
Change-Id: I0969e0c20848f202469acc85c3681437b8b62fdc
Diffstat (limited to 'internal/jsonrpc2/serve.go')
-rw-r--r--internal/jsonrpc2/serve.go73
1 files changed, 48 insertions, 25 deletions
diff --git a/internal/jsonrpc2/serve.go b/internal/jsonrpc2/serve.go
index b9e31a857..d58797152 100644
--- a/internal/jsonrpc2/serve.go
+++ b/internal/jsonrpc2/serve.go
@@ -6,7 +6,6 @@ package jsonrpc2
import (
"context"
- "fmt"
"io"
"net"
"os"
@@ -65,47 +64,69 @@ func ListenAndServe(ctx context.Context, network, addr string, server StreamServ
// the provided server. If idleTimeout is non-zero, ListenAndServe exits after
// there are no clients for this duration, otherwise it exits only on error.
func Serve(ctx context.Context, ln net.Listener, server StreamServer, idleTimeout time.Duration) error {
- ctx, cancel := context.WithCancel(ctx)
- defer cancel()
- // Max duration: ~290 years; surely that's long enough.
- const forever = 1<<63 - 1
- if idleTimeout <= 0 {
- idleTimeout = forever
- }
- connTimer := time.NewTimer(idleTimeout)
-
newConns := make(chan net.Conn)
- doneListening := make(chan error)
closedConns := make(chan error)
-
+ activeConns := 0
+ var acceptErr error
go func() {
+ defer close(newConns)
for {
- nc, err := ln.Accept()
- if err != nil {
- select {
- case doneListening <- fmt.Errorf("Accept(): %w", err):
- case <-ctx.Done():
- }
+ var nc net.Conn
+ nc, acceptErr = ln.Accept()
+ if acceptErr != nil {
return
}
newConns <- nc
}
}()
- activeConns := 0
+ ctx, cancel := context.WithCancel(ctx)
+ defer func() {
+ // Signal the Accept goroutine to stop immediately
+ // and terminate all newly-accepted connections until it returns.
+ ln.Close()
+ for nc := range newConns {
+ nc.Close()
+ }
+ // Cancel pending ServeStream callbacks and wait for them to finish.
+ cancel()
+ for activeConns > 0 {
+ err := <-closedConns
+ if !isClosingError(err) {
+ event.Error(ctx, "closed a connection", err)
+ }
+ activeConns--
+ }
+ }()
+
+ // Max duration: ~290 years; surely that's long enough.
+ const forever = 1<<63 - 1
+ if idleTimeout <= 0 {
+ idleTimeout = forever
+ }
+ connTimer := time.NewTimer(idleTimeout)
+ defer connTimer.Stop()
+
for {
select {
- case netConn := <-newConns:
+ case netConn, ok := <-newConns:
+ if !ok {
+ return acceptErr
+ }
+ if activeConns == 0 && !connTimer.Stop() {
+ // connTimer.C may receive a value even after Stop returns.
+ // (See https://golang.org/issue/37196.)
+ <-connTimer.C
+ }
activeConns++
- connTimer.Stop()
stream := NewHeaderStream(netConn)
go func() {
conn := NewConn(stream)
- closedConns <- server.ServeStream(ctx, conn)
+ err := server.ServeStream(ctx, conn)
stream.Close()
+ closedConns <- err
}()
- case err := <-doneListening:
- return err
+
case err := <-closedConns:
if !isClosingError(err) {
event.Error(ctx, "closed a connection", err)
@@ -114,10 +135,12 @@ func Serve(ctx context.Context, ln net.Listener, server StreamServer, idleTimeou
if activeConns == 0 {
connTimer.Reset(idleTimeout)
}
+
case <-connTimer.C:
return ErrIdleTimeout
+
case <-ctx.Done():
- return ctx.Err()
+ return nil
}
}
}