gaper/runner.go

165 lines
3.5 KiB
Go
Raw Normal View History

package gaper
2018-06-16 19:22:21 -05:00
import (
2018-06-20 20:40:09 -05:00
"errors"
2018-06-16 19:22:21 -05:00
"fmt"
"io"
"os"
"os/exec"
"runtime"
2018-07-12 08:27:07 -05:00
"syscall"
2018-06-16 19:22:21 -05:00
"time"
)
2018-06-20 21:04:16 -05:00
// OSWindows is used to check if current OS is a Windows
2018-06-16 19:22:21 -05:00
const OSWindows = "windows"
2018-06-20 20:40:09 -05:00
// os errors
var errFinished = errors.New("os: process already finished")
2018-06-20 21:04:16 -05:00
// Runner is a interface for the run process
2018-06-16 19:22:21 -05:00
type Runner interface {
Run() (*exec.Cmd, error)
Kill() error
2018-06-18 22:50:05 -05:00
Errors() chan error
Exited() bool
IsRunning() bool
2018-07-12 08:27:07 -05:00
ExitStatus(err error) int
2018-06-16 19:22:21 -05:00
}
type runner struct {
bin string
args []string
writerStdout io.Writer
writerStderr io.Writer
command *exec.Cmd
starttime time.Time
errors chan error
2018-06-20 20:40:09 -05:00
end chan bool // used internally by Kill to wait a process die
2018-06-16 19:22:21 -05:00
}
2018-06-20 21:04:16 -05:00
// NewRunner creates a new runner
func NewRunner(wStdout io.Writer, wStderr io.Writer, bin string, args []string) Runner {
2018-06-16 19:22:21 -05:00
return &runner{
bin: bin,
args: args,
writerStdout: wStdout,
writerStderr: wStderr,
starttime: time.Now(),
errors: make(chan error),
2018-06-20 20:40:09 -05:00
end: make(chan bool),
2018-06-16 19:22:21 -05:00
}
}
2018-06-20 21:04:16 -05:00
// Run executes the project binary
2018-06-16 19:22:21 -05:00
func (r *runner) Run() (*exec.Cmd, error) {
logger.Info("Starting program")
2018-06-20 20:40:09 -05:00
if r.command != nil && !r.Exited() {
2018-06-16 19:22:21 -05:00
return r.command, nil
}
2018-06-20 20:40:09 -05:00
if err := r.runBin(); err != nil {
return nil, fmt.Errorf("error running: %v", err)
}
2018-06-16 19:22:21 -05:00
return r.command, nil
}
2018-06-20 21:04:16 -05:00
// Kill the current process running for the Golang project
2018-06-20 22:48:36 -05:00
func (r *runner) Kill() error { // nolint gocyclo
2018-06-16 19:22:21 -05:00
if r.command == nil || r.command.Process == nil {
return nil
}
done := make(chan error)
go func() {
2018-06-20 20:40:09 -05:00
<-r.end
2018-06-16 19:22:21 -05:00
close(done)
}()
// Trying a "soft" kill first
if runtime.GOOS == OSWindows {
if err := r.command.Process.Kill(); err != nil {
return err
}
} else if err := r.command.Process.Signal(os.Interrupt); err != nil {
return err
}
// Wait for our process to die before we return or hard kill after 3 sec
select {
case <-time.After(3 * time.Second):
2018-06-20 21:15:58 -05:00
if err := r.command.Process.Kill(); err != nil {
errMsg := err.Error()
// ignore error if the processed has been killed already
if errMsg != errFinished.Error() && errMsg != os.ErrInvalid.Error() {
return fmt.Errorf("failed to kill: %v", err)
}
2018-06-16 19:22:21 -05:00
}
case <-done:
}
r.command = nil
return nil
}
2018-06-20 21:04:16 -05:00
// Exited checks if the process has exited
2018-06-16 19:22:21 -05:00
func (r *runner) Exited() bool {
return r.command != nil && r.command.ProcessState != nil && r.command.ProcessState.Exited()
}
// IsRunning returns if the process is running
func (r *runner) IsRunning() bool {
return r.command != nil && r.command.Process != nil && r.command.Process.Pid > 0
}
2018-06-20 21:04:16 -05:00
// Errors get errors occurred during the build
2018-06-18 22:50:05 -05:00
func (r *runner) Errors() chan error {
return r.errors
}
2018-07-12 08:27:07 -05:00
// ExitStatus resolves the exit status
func (r *runner) ExitStatus(err error) int {
var exitStatus int
if exiterr, ok := err.(*exec.ExitError); ok {
if status, oks := exiterr.Sys().(syscall.WaitStatus); oks {
exitStatus = status.ExitStatus()
}
}
return exitStatus
}
2018-06-16 19:22:21 -05:00
func (r *runner) runBin() error {
r.command = exec.Command(r.bin, r.args...) // nolint gas
stdout, err := r.command.StdoutPipe()
if err != nil {
return err
}
stderr, err := r.command.StderrPipe()
if err != nil {
return err
}
// TODO: handle or log errors
go io.Copy(r.writerStdout, stdout) // nolint errcheck
go io.Copy(r.writerStderr, stderr) // nolint errcheck
2018-06-16 19:22:21 -05:00
err = r.command.Start()
if err != nil {
return err
}
r.starttime = time.Now()
2018-06-18 22:50:05 -05:00
// wait for exit errors
go func() {
r.errors <- r.command.Wait()
2018-06-20 20:40:09 -05:00
r.end <- true
2018-06-18 22:50:05 -05:00
}()
2018-06-16 19:22:21 -05:00
return nil
}