github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/jsonrpc2/serve.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package jsonrpc2 6 7 import ( 8 "context" 9 "io" 10 "net" 11 "os" 12 "time" 13 14 "github.com/jhump/golang-x-tools/internal/event" 15 errors "golang.org/x/xerrors" 16 ) 17 18 // NOTE: This file provides an experimental API for serving multiple remote 19 // jsonrpc2 clients over the network. For now, it is intentionally similar to 20 // net/http, but that may change in the future as we figure out the correct 21 // semantics. 22 23 // A StreamServer is used to serve incoming jsonrpc2 clients communicating over 24 // a newly created connection. 25 type StreamServer interface { 26 ServeStream(context.Context, Conn) error 27 } 28 29 // The ServerFunc type is an adapter that implements the StreamServer interface 30 // using an ordinary function. 31 type ServerFunc func(context.Context, Conn) error 32 33 // ServeStream calls f(ctx, s). 34 func (f ServerFunc) ServeStream(ctx context.Context, c Conn) error { 35 return f(ctx, c) 36 } 37 38 // HandlerServer returns a StreamServer that handles incoming streams using the 39 // provided handler. 40 func HandlerServer(h Handler) StreamServer { 41 return ServerFunc(func(ctx context.Context, conn Conn) error { 42 conn.Go(ctx, h) 43 <-conn.Done() 44 return conn.Err() 45 }) 46 } 47 48 // ListenAndServe starts an jsonrpc2 server on the given address. If 49 // idleTimeout is non-zero, ListenAndServe exits after there are no clients for 50 // this duration, otherwise it exits only on error. 51 func ListenAndServe(ctx context.Context, network, addr string, server StreamServer, idleTimeout time.Duration) error { 52 ln, err := net.Listen(network, addr) 53 if err != nil { 54 return err 55 } 56 defer ln.Close() 57 if network == "unix" { 58 defer os.Remove(addr) 59 } 60 return Serve(ctx, ln, server, idleTimeout) 61 } 62 63 // Serve accepts incoming connections from the network, and handles them using 64 // the provided server. If idleTimeout is non-zero, ListenAndServe exits after 65 // there are no clients for this duration, otherwise it exits only on error. 66 func Serve(ctx context.Context, ln net.Listener, server StreamServer, idleTimeout time.Duration) error { 67 newConns := make(chan net.Conn) 68 closedConns := make(chan error) 69 activeConns := 0 70 var acceptErr error 71 go func() { 72 defer close(newConns) 73 for { 74 var nc net.Conn 75 nc, acceptErr = ln.Accept() 76 if acceptErr != nil { 77 return 78 } 79 newConns <- nc 80 } 81 }() 82 83 ctx, cancel := context.WithCancel(ctx) 84 defer func() { 85 // Signal the Accept goroutine to stop immediately 86 // and terminate all newly-accepted connections until it returns. 87 ln.Close() 88 for nc := range newConns { 89 nc.Close() 90 } 91 // Cancel pending ServeStream callbacks and wait for them to finish. 92 cancel() 93 for activeConns > 0 { 94 err := <-closedConns 95 if !isClosingError(err) { 96 event.Error(ctx, "closed a connection", err) 97 } 98 activeConns-- 99 } 100 }() 101 102 // Max duration: ~290 years; surely that's long enough. 103 const forever = 1<<63 - 1 104 if idleTimeout <= 0 { 105 idleTimeout = forever 106 } 107 connTimer := time.NewTimer(idleTimeout) 108 defer connTimer.Stop() 109 110 for { 111 select { 112 case netConn, ok := <-newConns: 113 if !ok { 114 return acceptErr 115 } 116 if activeConns == 0 && !connTimer.Stop() { 117 // connTimer.C may receive a value even after Stop returns. 118 // (See https://golang.org/issue/37196.) 119 <-connTimer.C 120 } 121 activeConns++ 122 stream := NewHeaderStream(netConn) 123 go func() { 124 conn := NewConn(stream) 125 err := server.ServeStream(ctx, conn) 126 stream.Close() 127 closedConns <- err 128 }() 129 130 case err := <-closedConns: 131 if !isClosingError(err) { 132 event.Error(ctx, "closed a connection", err) 133 } 134 activeConns-- 135 if activeConns == 0 { 136 connTimer.Reset(idleTimeout) 137 } 138 139 case <-connTimer.C: 140 return ErrIdleTimeout 141 142 case <-ctx.Done(): 143 return nil 144 } 145 } 146 } 147 148 // isClosingError reports if the error occurs normally during the process of 149 // closing a network connection. It uses imperfect heuristics that err on the 150 // side of false negatives, and should not be used for anything critical. 151 func isClosingError(err error) bool { 152 if errors.Is(err, io.EOF) { 153 return true 154 } 155 // Per https://github.com/golang/go/issues/4373, this error string should not 156 // change. This is not ideal, but since the worst that could happen here is 157 // some superfluous logging, it is acceptable. 158 if err.Error() == "use of closed network connection" { 159 return true 160 } 161 return false 162 }