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  }