github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/lsprpc/dialer.go (about)

     1  // Copyright 2021 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 lsprpc
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  	"net"
    12  	"os"
    13  	"time"
    14  
    15  	exec "golang.org/x/sys/execabs"
    16  	"github.com/powerman/golang-tools/internal/event"
    17  	errors "golang.org/x/xerrors"
    18  )
    19  
    20  // AutoNetwork is the pseudo network type used to signal that gopls should use
    21  // automatic discovery to resolve a remote address.
    22  const AutoNetwork = "auto"
    23  
    24  // An AutoDialer is a jsonrpc2 dialer that understands the 'auto' network.
    25  type AutoDialer struct {
    26  	network, addr string // the 'real' network and address
    27  	isAuto        bool   // whether the server is on the 'auto' network
    28  
    29  	executable string
    30  	argFunc    func(network, addr string) []string
    31  }
    32  
    33  func NewAutoDialer(rawAddr string, argFunc func(network, addr string) []string) (*AutoDialer, error) {
    34  	d := AutoDialer{
    35  		argFunc: argFunc,
    36  	}
    37  	d.network, d.addr = ParseAddr(rawAddr)
    38  	if d.network == AutoNetwork {
    39  		d.isAuto = true
    40  		bin, err := os.Executable()
    41  		if err != nil {
    42  			return nil, errors.Errorf("getting executable: %w", err)
    43  		}
    44  		d.executable = bin
    45  		d.network, d.addr = autoNetworkAddress(bin, d.addr)
    46  	}
    47  	return &d, nil
    48  }
    49  
    50  // Dial implements the jsonrpc2.Dialer interface.
    51  func (d *AutoDialer) Dial(ctx context.Context) (io.ReadWriteCloser, error) {
    52  	conn, err := d.dialNet(ctx)
    53  	return conn, err
    54  }
    55  
    56  // TODO(rFindley): remove this once we no longer need to integrate with v1 of
    57  // the jsonrpc2 package.
    58  func (d *AutoDialer) dialNet(ctx context.Context) (net.Conn, error) {
    59  	// Attempt to verify that we own the remote. This is imperfect, but if we can
    60  	// determine that the remote is owned by a different user, we should fail.
    61  	ok, err := verifyRemoteOwnership(d.network, d.addr)
    62  	if err != nil {
    63  		// If the ownership check itself failed, we fail open but log an error to
    64  		// the user.
    65  		event.Error(ctx, "unable to check daemon socket owner, failing open", err)
    66  	} else if !ok {
    67  		// We successfully checked that the socket is not owned by us, we fail
    68  		// closed.
    69  		return nil, fmt.Errorf("socket %q is owned by a different user", d.addr)
    70  	}
    71  	const dialTimeout = 1 * time.Second
    72  	// Try dialing our remote once, in case it is already running.
    73  	netConn, err := net.DialTimeout(d.network, d.addr, dialTimeout)
    74  	if err == nil {
    75  		return netConn, nil
    76  	}
    77  	if d.isAuto && d.argFunc != nil {
    78  		if d.network == "unix" {
    79  			// Sometimes the socketfile isn't properly cleaned up when the server
    80  			// shuts down. Since we have already tried and failed to dial this
    81  			// address, it should *usually* be safe to remove the socket before
    82  			// binding to the address.
    83  			// TODO(rfindley): there is probably a race here if multiple server
    84  			// instances are simultaneously starting up.
    85  			if _, err := os.Stat(d.addr); err == nil {
    86  				if err := os.Remove(d.addr); err != nil {
    87  					return nil, errors.Errorf("removing remote socket file: %w", err)
    88  				}
    89  			}
    90  		}
    91  		args := d.argFunc(d.network, d.addr)
    92  		cmd := exec.Command(d.executable, args...)
    93  		if err := runRemote(cmd); err != nil {
    94  			return nil, err
    95  		}
    96  	}
    97  
    98  	const retries = 5
    99  	// It can take some time for the newly started server to bind to our address,
   100  	// so we retry for a bit.
   101  	for retry := 0; retry < retries; retry++ {
   102  		startDial := time.Now()
   103  		netConn, err = net.DialTimeout(d.network, d.addr, dialTimeout)
   104  		if err == nil {
   105  			return netConn, nil
   106  		}
   107  		event.Log(ctx, fmt.Sprintf("failed attempt #%d to connect to remote: %v\n", retry+2, err))
   108  		// In case our failure was a fast-failure, ensure we wait at least
   109  		// f.dialTimeout before trying again.
   110  		if retry != retries-1 {
   111  			time.Sleep(dialTimeout - time.Since(startDial))
   112  		}
   113  	}
   114  	return nil, errors.Errorf("dialing remote: %w", err)
   115  }