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  }