github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/jsonrpc2_v2/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 "runtime" 11 "strings" 12 "sync" 13 "syscall" 14 "time" 15 16 errors "golang.org/x/xerrors" 17 ) 18 19 // Listener is implemented by protocols to accept new inbound connections. 20 type Listener interface { 21 // Accept an inbound connection to a server. 22 // It must block until an inbound connection is made, or the listener is 23 // shut down. 24 Accept(context.Context) (io.ReadWriteCloser, error) 25 26 // Close is used to ask a listener to stop accepting new connections. 27 Close() error 28 29 // Dialer returns a dialer that can be used to connect to this listener 30 // locally. 31 // If a listener does not implement this it will return a nil. 32 Dialer() Dialer 33 } 34 35 // Dialer is used by clients to dial a server. 36 type Dialer interface { 37 // Dial returns a new communication byte stream to a listening server. 38 Dial(ctx context.Context) (io.ReadWriteCloser, error) 39 } 40 41 // Server is a running server that is accepting incoming connections. 42 type Server struct { 43 listener Listener 44 binder Binder 45 async *async 46 } 47 48 // Dial uses the dialer to make a new connection, wraps the returned 49 // reader and writer using the framer to make a stream, and then builds 50 // a connection on top of that stream using the binder. 51 func Dial(ctx context.Context, dialer Dialer, binder Binder) (*Connection, error) { 52 // dial a server 53 rwc, err := dialer.Dial(ctx) 54 if err != nil { 55 return nil, err 56 } 57 return newConnection(ctx, rwc, binder) 58 } 59 60 // Serve starts a new server listening for incoming connections and returns 61 // it. 62 // This returns a fully running and connected server, it does not block on 63 // the listener. 64 // You can call Wait to block on the server, or Shutdown to get the sever to 65 // terminate gracefully. 66 // To notice incoming connections, use an intercepting Binder. 67 func Serve(ctx context.Context, listener Listener, binder Binder) (*Server, error) { 68 server := &Server{ 69 listener: listener, 70 binder: binder, 71 async: newAsync(), 72 } 73 go server.run(ctx) 74 return server, nil 75 } 76 77 // Wait returns only when the server has shut down. 78 func (s *Server) Wait() error { 79 return s.async.wait() 80 } 81 82 // run accepts incoming connections from the listener, 83 // If IdleTimeout is non-zero, run exits after there are no clients for this 84 // duration, otherwise it exits only on error. 85 func (s *Server) run(ctx context.Context) { 86 defer s.async.done() 87 var activeConns []*Connection 88 for { 89 // we never close the accepted connection, we rely on the other end 90 // closing or the socket closing itself naturally 91 rwc, err := s.listener.Accept(ctx) 92 if err != nil { 93 if !isClosingError(err) { 94 s.async.setError(err) 95 } 96 // we are done generating new connections for good 97 break 98 } 99 100 // see if any connections were closed while we were waiting 101 activeConns = onlyActive(activeConns) 102 103 // a new inbound connection, 104 conn, err := newConnection(ctx, rwc, s.binder) 105 if err != nil { 106 if !isClosingError(err) { 107 s.async.setError(err) 108 } 109 continue 110 } 111 activeConns = append(activeConns, conn) 112 } 113 114 // wait for all active conns to finish 115 for _, c := range activeConns { 116 c.Wait() 117 } 118 } 119 120 func onlyActive(conns []*Connection) []*Connection { 121 i := 0 122 for _, c := range conns { 123 if !c.async.isDone() { 124 conns[i] = c 125 i++ 126 } 127 } 128 // trim the slice down 129 return conns[:i] 130 } 131 132 // isClosingError reports if the error occurs normally during the process of 133 // closing a network connection. It uses imperfect heuristics that err on the 134 // side of false negatives, and should not be used for anything critical. 135 func isClosingError(err error) bool { 136 if err == nil { 137 return false 138 } 139 // Fully unwrap the error, so the following tests work. 140 for wrapped := err; wrapped != nil; wrapped = errors.Unwrap(err) { 141 err = wrapped 142 } 143 144 // Was it based on an EOF error? 145 if err == io.EOF { 146 return true 147 } 148 149 // Was it based on a closed pipe? 150 if err == io.ErrClosedPipe { 151 return true 152 } 153 154 // Per https://github.com/golang/go/issues/4373, this error string should not 155 // change. This is not ideal, but since the worst that could happen here is 156 // some superfluous logging, it is acceptable. 157 if err.Error() == "use of closed network connection" { 158 return true 159 } 160 161 if runtime.GOOS == "plan9" { 162 // Error reading from a closed connection. 163 if err == syscall.EINVAL { 164 return true 165 } 166 // Error trying to accept a new connection from a closed listener. 167 if strings.HasSuffix(err.Error(), " listen hungup") { 168 return true 169 } 170 } 171 return false 172 } 173 174 // NewIdleListener wraps a listener with an idle timeout. 175 // When there are no active connections for at least the timeout duration a 176 // call to accept will fail with ErrIdleTimeout. 177 func NewIdleListener(timeout time.Duration, wrap Listener) Listener { 178 l := &idleListener{ 179 timeout: timeout, 180 wrapped: wrap, 181 newConns: make(chan *idleCloser), 182 closed: make(chan struct{}), 183 wasTimeout: make(chan struct{}), 184 } 185 go l.run() 186 return l 187 } 188 189 type idleListener struct { 190 wrapped Listener 191 timeout time.Duration 192 newConns chan *idleCloser 193 closed chan struct{} 194 wasTimeout chan struct{} 195 closeOnce sync.Once 196 } 197 198 type idleCloser struct { 199 wrapped io.ReadWriteCloser 200 closed chan struct{} 201 closeOnce sync.Once 202 } 203 204 func (c *idleCloser) Read(p []byte) (int, error) { 205 n, err := c.wrapped.Read(p) 206 if err != nil && isClosingError(err) { 207 c.closeOnce.Do(func() { close(c.closed) }) 208 } 209 return n, err 210 } 211 212 func (c *idleCloser) Write(p []byte) (int, error) { 213 // we do not close on write failure, we rely on the wrapped writer to do that 214 // if it is appropriate, which we will detect in the next read. 215 return c.wrapped.Write(p) 216 } 217 218 func (c *idleCloser) Close() error { 219 // we rely on closing the wrapped stream to signal to the next read that we 220 // are closed, rather than triggering the closed signal directly 221 return c.wrapped.Close() 222 } 223 224 func (l *idleListener) Accept(ctx context.Context) (io.ReadWriteCloser, error) { 225 rwc, err := l.wrapped.Accept(ctx) 226 if err != nil { 227 if isClosingError(err) { 228 // underlying listener was closed 229 l.closeOnce.Do(func() { close(l.closed) }) 230 // was it closed because of the idle timeout? 231 select { 232 case <-l.wasTimeout: 233 err = ErrIdleTimeout 234 default: 235 } 236 } 237 return nil, err 238 } 239 conn := &idleCloser{ 240 wrapped: rwc, 241 closed: make(chan struct{}), 242 } 243 l.newConns <- conn 244 return conn, err 245 } 246 247 func (l *idleListener) Close() error { 248 defer l.closeOnce.Do(func() { close(l.closed) }) 249 return l.wrapped.Close() 250 } 251 252 func (l *idleListener) Dialer() Dialer { 253 return l.wrapped.Dialer() 254 } 255 256 func (l *idleListener) run() { 257 var conns []*idleCloser 258 for { 259 var firstClosed chan struct{} // left at nil if there are no active conns 260 var timeout <-chan time.Time // left at nil if there are active conns 261 if len(conns) > 0 { 262 firstClosed = conns[0].closed 263 } else { 264 timeout = time.After(l.timeout) 265 } 266 select { 267 case <-l.closed: 268 // the main listener closed, no need to keep going 269 return 270 case conn := <-l.newConns: 271 // a new conn arrived, add it to the list 272 conns = append(conns, conn) 273 case <-timeout: 274 // we timed out, only happens when there are no active conns 275 // close the underlying listener, and allow the normal closing process to happen 276 close(l.wasTimeout) 277 l.wrapped.Close() 278 case <-firstClosed: 279 // a conn closed, remove it from the active list 280 conns = conns[:copy(conns, conns[1:])] 281 } 282 } 283 }