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