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 }