github.com/getgauge/gauge@v1.6.9/runner/legacyRunner.go (about) 1 /*---------------------------------------------------------------- 2 * Copyright (c) ThoughtWorks, Inc. 3 * Licensed under the Apache License, Version 2.0 4 * See LICENSE in the project root for license information. 5 *----------------------------------------------------------------*/ 6 7 package runner 8 9 import ( 10 "fmt" 11 "io" 12 "net" 13 "os/exec" 14 "sync" 15 "time" 16 17 "github.com/getgauge/gauge-proto/go/gauge_messages" 18 "github.com/getgauge/gauge/config" 19 "github.com/getgauge/gauge/conn" 20 "github.com/getgauge/gauge/logger" 21 "github.com/getgauge/gauge/manifest" 22 ) 23 24 type LegacyRunner struct { 25 mutex *sync.Mutex 26 Cmd *exec.Cmd 27 connection net.Conn 28 errorChannel chan error 29 multiThreaded bool 30 lostContact bool 31 info *RunnerInfo 32 } 33 34 func (r *LegacyRunner) Alive() bool { 35 r.mutex.Lock() 36 ps := r.Cmd.ProcessState 37 r.mutex.Unlock() 38 return ps == nil || !ps.Exited() 39 } 40 41 func (r *LegacyRunner) EnsureConnected() bool { 42 if r.lostContact { 43 return false 44 } 45 c := r.connection 46 err := c.SetReadDeadline(time.Now()) 47 if err != nil { 48 logger.Fatalf(true, "Unable to SetReadDeadLine on runner: %s", err.Error()) 49 } 50 var one []byte 51 _, err = c.Read(one) 52 if err == io.EOF { 53 r.lostContact = true 54 logger.Fatalf(true, "Connection to runner with Pid %d lost. The runner probably quit unexpectedly. Inspect logs for potential reasons. Error : %s", r.Cmd.Process.Pid, err.Error()) 55 } 56 opErr, ok := err.(*net.OpError) 57 if ok && !(opErr.Temporary() || opErr.Timeout()) { 58 r.lostContact = true 59 logger.Fatalf(true, "Connection to runner with Pid %d lost. The runner probably quit unexpectedly. Inspect logs for potential reasons. Error : %s", r.Cmd.Process.Pid, err.Error()) 60 } 61 var zero time.Time 62 err = c.SetReadDeadline(zero) 63 if err != nil { 64 logger.Fatalf(true, "Unable to SetReadDeadLine on runner: %s", err.Error()) 65 } 66 67 return true 68 } 69 70 func (r *LegacyRunner) IsMultithreaded() bool { 71 return r.multiThreaded 72 } 73 74 func (r *LegacyRunner) Kill() error { 75 if r.Alive() { 76 defer r.connection.Close() 77 logger.Debug(true, "Sending kill message to runner.") 78 err := conn.SendProcessKillMessage(r.connection) 79 if err != nil { 80 return err 81 } 82 exited := make(chan bool, 1) 83 go func() { 84 for { 85 if r.Alive() { 86 time.Sleep(100 * time.Millisecond) 87 } else { 88 exited <- true 89 return 90 } 91 } 92 }() 93 94 select { 95 case done := <-exited: 96 if done { 97 logger.Debugf(true, "Runner with PID:%d has exited", r.Cmd.Process.Pid) 98 return nil 99 } 100 case <-time.After(config.PluginKillTimeout()): 101 logger.Warningf(true, "Killing runner with PID:%d forcefully", r.Cmd.Process.Pid) 102 return r.killRunner() 103 } 104 } 105 return nil 106 } 107 108 func (r *LegacyRunner) Connection() net.Conn { 109 return r.connection 110 } 111 112 func (r *LegacyRunner) killRunner() error { 113 return r.Cmd.Process.Kill() 114 } 115 116 // Info gives the information about runner 117 func (r *LegacyRunner) Info() *RunnerInfo { 118 return r.info 119 } 120 121 func (r *LegacyRunner) Pid() int { 122 return r.Cmd.Process.Pid 123 } 124 125 // ExecuteAndGetStatus invokes the runner with a request and waits for response. error is thrown only when unable to connect to runner 126 func (r *LegacyRunner) ExecuteAndGetStatus(message *gauge_messages.Message) *gauge_messages.ProtoExecutionResult { 127 if !r.EnsureConnected() { 128 return nil 129 } 130 response, err := conn.GetResponseForMessageWithTimeout(message, r.connection, 0) 131 if err != nil { 132 return &gauge_messages.ProtoExecutionResult{Failed: true, ErrorMessage: err.Error()} 133 } 134 135 if response.GetMessageType() == gauge_messages.Message_ExecutionStatusResponse { 136 executionResult := response.GetExecutionStatusResponse().GetExecutionResult() 137 if executionResult == nil { 138 errMsg := "ProtoExecutionResult obtained is nil" 139 logger.Error(true, errMsg) 140 return errorResult(errMsg) 141 } 142 return executionResult 143 } 144 errMsg := fmt.Sprintf("Expected ExecutionStatusResponse. Obtained: %s", response.GetMessageType()) 145 logger.Error(true, errMsg) 146 return errorResult(errMsg) 147 } 148 149 func (r *LegacyRunner) ExecuteMessageWithTimeout(message *gauge_messages.Message) (*gauge_messages.Message, error) { 150 r.EnsureConnected() 151 return conn.GetResponseForMessageWithTimeout(message, r.Connection(), config.RunnerRequestTimeout()) 152 } 153 154 // StartLegacyRunner looks for a runner configuration inside the runner directory 155 // finds the runner configuration matching to the manifest and executes the commands for the current OS 156 func StartLegacyRunner(manifest *manifest.Manifest, port string, outputStreamWriter *logger.LogWriter, killChannel chan bool, debug bool) (*LegacyRunner, error) { 157 cmd, r, err := runRunnerCommand(manifest, port, debug, outputStreamWriter) 158 if err != nil { 159 return nil, err 160 } 161 go func() { 162 <-killChannel 163 err := cmd.Process.Kill() 164 if err != nil { 165 logger.Errorf(false, "Unable to kill %s with PID %d : %s", cmd.Path, cmd.Process.Pid, err.Error()) 166 } 167 }() 168 // Wait for the process to exit so we will get a detailed error message 169 errChannel := make(chan error) 170 testRunner := &LegacyRunner{info: r, Cmd: cmd, errorChannel: errChannel, mutex: &sync.Mutex{}, multiThreaded: r.Multithreaded} 171 testRunner.waitAndGetErrorMessage() 172 return testRunner, nil 173 }