forked from codegangsta/gin
/
runner.go
116 lines (99 loc) · 1.93 KB
/
runner.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package gin
import (
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"time"
)
type Runner interface {
Run() (*exec.Cmd, error)
Info() (os.FileInfo, error)
SetWriter(io.Writer)
Kill() error
}
type runner struct {
bin string
args []string
writer io.Writer
command *exec.Cmd
starttime time.Time
}
func NewRunner(bin string, args ...string) Runner {
return &runner{
bin: bin,
args: args,
writer: ioutil.Discard,
starttime: time.Now(),
}
}
func (r *runner) Run() (*exec.Cmd, error) {
if r.needsRefresh() {
r.Kill()
}
if r.command == nil {
err := r.runBin()
time.Sleep(250 * time.Millisecond)
return r.command, err
} else {
return r.command, nil
}
}
func (r *runner) Info() (os.FileInfo, error) {
return os.Stat(r.bin)
}
func (r *runner) SetWriter(writer io.Writer) {
r.writer = writer
}
func (r *runner) Kill() error {
if r.command != nil && r.command.Process != nil {
done := make(chan error)
go func() {
r.command.Wait()
close(done)
}()
//Trying a "soft" kill first
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):
log.Print("failed to kill!")
if err := r.command.Process.Kill(); err != nil {
log.Println("failed to kill: ", err)
}
case <-done:
}
r.command = nil
}
return nil
}
func (r *runner) runBin() error {
r.command = exec.Command(r.bin, r.args...)
stdout, err := r.command.StdoutPipe()
if err != nil {
return err
}
stderr, err := r.command.StderrPipe()
if err != nil {
return err
}
err = r.command.Start()
if err != nil {
return err
}
r.starttime = time.Now()
go io.Copy(r.writer, stdout)
go io.Copy(r.writer, stderr)
return nil
}
func (r *runner) needsRefresh() bool {
info, err := r.Info()
if err != nil {
return false
} else {
return info.ModTime().After(r.starttime)
}
}