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  }