github.com/mutagen-io/mutagen@v0.18.0-rc1/cmd/mutagen/daemon/connect.go (about) 1 package daemon 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "os" 8 "time" 9 10 "google.golang.org/grpc" 11 12 "github.com/mutagen-io/mutagen/cmd" 13 "github.com/mutagen-io/mutagen/cmd/external" 14 15 "github.com/mutagen-io/mutagen/pkg/daemon" 16 "github.com/mutagen-io/mutagen/pkg/grpcutil" 17 "github.com/mutagen-io/mutagen/pkg/ipc" 18 "github.com/mutagen-io/mutagen/pkg/mutagen" 19 daemonsvc "github.com/mutagen-io/mutagen/pkg/service/daemon" 20 ) 21 22 const ( 23 // dialTimeout is the timeout to use when attempting to connect to the 24 // daemon IPC endpoint. 25 dialTimeout = 500 * time.Millisecond 26 // autostartWaitInterval is the wait period between reconnect attempts after 27 // autostarting the daemon. 28 autostartWaitInterval = 100 * time.Millisecond 29 // autostartRetryCount is the number of times to try reconnecting after 30 // autostarting the daemon. 31 autostartRetryCount = 10 32 ) 33 34 // Connect creates a new daemon client connection and optionally verifies that 35 // the daemon version matches the current process' version. 36 func Connect(autostart, enforceVersionMatch bool) (*grpc.ClientConn, error) { 37 // Compute the path to the daemon IPC endpoint. 38 endpoint, err := daemon.EndpointPath() 39 if err != nil { 40 return nil, fmt.Errorf("unable to compute endpoint path: %w", err) 41 } 42 43 // Check if autostart has been disabled programmatically or by an 44 // environment variable. 45 if external.DisableDaemonAutostart || os.Getenv("MUTAGEN_DISABLE_AUTOSTART") == "1" { 46 autostart = false 47 } 48 49 // Create a status line printer and defer a clear. 50 statusLinePrinter := &cmd.StatusLinePrinter{UseStandardError: true} 51 defer statusLinePrinter.BreakIfPopulated() 52 53 // Perform dialing in a loop until failure or success. 54 remainingPostAutostatAttempts := autostartRetryCount 55 invokedStart := false 56 var connection *grpc.ClientConn 57 for { 58 // Create a context to timeout the dial. 59 ctx, cancel := context.WithTimeout(context.Background(), dialTimeout) 60 61 // Attempt to dial. 62 connection, err = grpc.DialContext( 63 ctx, endpoint, 64 grpc.WithInsecure(), 65 grpc.WithContextDialer(ipc.DialContext), 66 grpc.WithBlock(), 67 grpc.WithDefaultCallOptions( 68 grpc.MaxCallSendMsgSize(grpcutil.MaximumMessageSize), 69 grpc.MaxCallRecvMsgSize(grpcutil.MaximumMessageSize), 70 ), 71 ) 72 73 // Cancel the dialing context. If the dialing operation has already 74 // succeeded, this has no effect, but it is necessary to clean up the 75 // Goroutine that backs the context. 76 cancel() 77 78 // Check for errors. 79 if err != nil { 80 // Handle failure due to timeouts. 81 if err == context.DeadlineExceeded { 82 // If autostart is enabled, and we have attempts remaining, then 83 // try autostarting, waiting, and retrying. 84 if autostart && remainingPostAutostatAttempts > 0 { 85 if !invokedStart { 86 statusLinePrinter.Print("Attempting to start Mutagen daemon...") 87 startMain(nil, nil) 88 invokedStart = true 89 } 90 time.Sleep(autostartWaitInterval) 91 remainingPostAutostatAttempts-- 92 continue 93 } 94 95 // Otherwise just fail due to the timeout. 96 return nil, errors.New("connection timed out (is the daemon running?)") 97 } 98 99 // If we failed for any other reason, then bail. 100 return nil, err 101 } 102 103 // Print a notice if we started the daemon. 104 if invokedStart { 105 statusLinePrinter.Clear() 106 statusLinePrinter.Print("Started Mutagen daemon in background (terminate with \"mutagen daemon stop\")") 107 } 108 109 // We've successfully dialed, so break out of the dialing loop. 110 break 111 } 112 113 // If requested, verify that the daemon version matches the current process' 114 // version. 115 if enforceVersionMatch { 116 daemonService := daemonsvc.NewDaemonClient(connection) 117 version, err := daemonService.Version(context.Background(), &daemonsvc.VersionRequest{}) 118 if err != nil { 119 connection.Close() 120 return nil, fmt.Errorf("unable to query daemon version: %w", err) 121 } 122 versionMatch := version.Major == mutagen.VersionMajor && 123 version.Minor == mutagen.VersionMinor && 124 version.Patch == mutagen.VersionPatch && 125 version.Tag == mutagen.VersionTag 126 if !versionMatch { 127 connection.Close() 128 return nil, errors.New("client/daemon version mismatch (daemon restart recommended)") 129 } 130 } 131 132 // Success. 133 return connection, nil 134 }