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