| // Copyright 2020 The Go Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style | 
 | // license that can be found in the LICENSE file. | 
 |  | 
 | package jsonrpc2 | 
 |  | 
 | import ( | 
 | 	"context" | 
 | 	"errors" | 
 | 	"io" | 
 | 	"math" | 
 | 	"net" | 
 | 	"os" | 
 | 	"time" | 
 |  | 
 | 	"golang.org/x/tools/internal/event" | 
 | ) | 
 |  | 
 | // NOTE: This file provides an experimental API for serving multiple remote | 
 | // jsonrpc2 clients over the network. For now, it is intentionally similar to | 
 | // net/http, but that may change in the future as we figure out the correct | 
 | // semantics. | 
 |  | 
 | // A StreamServer is used to serve incoming jsonrpc2 clients communicating over | 
 | // a newly created connection. | 
 | type StreamServer interface { | 
 | 	ServeStream(context.Context, Conn) error | 
 | } | 
 |  | 
 | // The ServerFunc type is an adapter that implements the StreamServer interface | 
 | // using an ordinary function. | 
 | type ServerFunc func(context.Context, Conn) error | 
 |  | 
 | // ServeStream calls f(ctx, s). | 
 | func (f ServerFunc) ServeStream(ctx context.Context, c Conn) error { | 
 | 	return f(ctx, c) | 
 | } | 
 |  | 
 | // HandlerServer returns a StreamServer that handles incoming streams using the | 
 | // provided handler. | 
 | func HandlerServer(h Handler) StreamServer { | 
 | 	return ServerFunc(func(ctx context.Context, conn Conn) error { | 
 | 		conn.Go(ctx, h) | 
 | 		<-conn.Done() | 
 | 		return conn.Err() | 
 | 	}) | 
 | } | 
 |  | 
 | // ListenAndServe starts an jsonrpc2 server on the given address.  If | 
 | // idleTimeout is non-zero, ListenAndServe exits after there are no clients for | 
 | // this duration, otherwise it exits only on error. | 
 | func ListenAndServe(ctx context.Context, network, addr string, server StreamServer, idleTimeout time.Duration) error { | 
 | 	ln, err := net.Listen(network, addr) | 
 | 	if err != nil { | 
 | 		return err | 
 | 	} | 
 | 	defer ln.Close() | 
 | 	if network == "unix" { | 
 | 		defer os.Remove(addr) | 
 | 	} | 
 | 	return Serve(ctx, ln, server, idleTimeout) | 
 | } | 
 |  | 
 | // Serve accepts incoming connections from the network, and handles them using | 
 | // the provided server. If idleTimeout is non-zero, ListenAndServe exits after | 
 | // there are no clients for this duration, otherwise it exits only on error. | 
 | func Serve(ctx context.Context, ln net.Listener, server StreamServer, idleTimeout time.Duration) error { | 
 | 	newConns := make(chan net.Conn) | 
 | 	closedConns := make(chan error) | 
 | 	activeConns := 0 | 
 | 	var acceptErr error | 
 | 	go func() { | 
 | 		defer close(newConns) | 
 | 		for { | 
 | 			var nc net.Conn | 
 | 			nc, acceptErr = ln.Accept() | 
 | 			if acceptErr != nil { | 
 | 				return | 
 | 			} | 
 | 			newConns <- nc | 
 | 		} | 
 | 	}() | 
 |  | 
 | 	ctx, cancel := context.WithCancel(ctx) | 
 | 	defer func() { | 
 | 		// Signal the Accept goroutine to stop immediately | 
 | 		// and terminate all newly-accepted connections until it returns. | 
 | 		ln.Close() | 
 | 		for nc := range newConns { | 
 | 			nc.Close() | 
 | 		} | 
 | 		// Cancel pending ServeStream callbacks and wait for them to finish. | 
 | 		cancel() | 
 | 		for activeConns > 0 { | 
 | 			err := <-closedConns | 
 | 			if !isClosingError(err) { | 
 | 				event.Error(ctx, "closed a connection", err) | 
 | 			} | 
 | 			activeConns-- | 
 | 		} | 
 | 	}() | 
 |  | 
 | 	// Max duration: ~290 years; surely that's long enough. | 
 | 	const forever = math.MaxInt64 | 
 | 	if idleTimeout <= 0 { | 
 | 		idleTimeout = forever | 
 | 	} | 
 | 	connTimer := time.NewTimer(idleTimeout) | 
 | 	defer connTimer.Stop() | 
 |  | 
 | 	for { | 
 | 		select { | 
 | 		case netConn, ok := <-newConns: | 
 | 			if !ok { | 
 | 				return acceptErr | 
 | 			} | 
 | 			if activeConns == 0 && !connTimer.Stop() { | 
 | 				// connTimer.C may receive a value even after Stop returns. | 
 | 				// (See https://golang.org/issue/37196.) | 
 | 				<-connTimer.C | 
 | 			} | 
 | 			activeConns++ | 
 | 			stream := NewHeaderStream(netConn) | 
 | 			go func() { | 
 | 				conn := NewConn(stream) | 
 | 				err := server.ServeStream(ctx, conn) | 
 | 				stream.Close() | 
 | 				closedConns <- err | 
 | 			}() | 
 |  | 
 | 		case err := <-closedConns: | 
 | 			if !isClosingError(err) { | 
 | 				event.Error(ctx, "closed a connection", err) | 
 | 			} | 
 | 			activeConns-- | 
 | 			if activeConns == 0 { | 
 | 				connTimer.Reset(idleTimeout) | 
 | 			} | 
 |  | 
 | 		case <-connTimer.C: | 
 | 			return ErrIdleTimeout | 
 |  | 
 | 		case <-ctx.Done(): | 
 | 			return nil | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | // isClosingError reports if the error occurs normally during the process of | 
 | // closing a network connection. It uses imperfect heuristics that err on the | 
 | // side of false negatives, and should not be used for anything critical. | 
 | func isClosingError(err error) bool { | 
 | 	if errors.Is(err, io.EOF) { | 
 | 		return true | 
 | 	} | 
 | 	// Per https://github.com/golang/go/issues/4373, this error string should not | 
 | 	// change. This is not ideal, but since the worst that could happen here is | 
 | 	// some superfluous logging, it is acceptable. | 
 | 	if err.Error() == "use of closed network connection" { | 
 | 		return true | 
 | 	} | 
 | 	return false | 
 | } |