5
5
"bytes"
6
6
"context"
7
7
"fmt"
8
+ "os"
8
9
"os/exec"
9
10
"strings"
10
11
"time"
@@ -49,7 +50,7 @@ func (i *Invocation) RunRaw(ctx context.Context) (stdout *bytes.Buffer, stderr *
49
50
goArgs = append (goArgs , i .BuildFlags ... )
50
51
goArgs = append (goArgs , i .Args ... )
51
52
}
52
- cmd := exec .CommandContext ( ctx , "go" , goArgs ... )
53
+ cmd := exec .Command ( "go" , goArgs ... )
53
54
stdout = & bytes.Buffer {}
54
55
stderr = & bytes.Buffer {}
55
56
cmd .Stdout = stdout
@@ -65,7 +66,7 @@ func (i *Invocation) RunRaw(ctx context.Context) (stdout *bytes.Buffer, stderr *
65
66
66
67
defer func (start time.Time ) { log ("%s for %v" , time .Since (start ), cmdDebugStr (cmd )) }(time .Now ())
67
68
68
- rawError = cmd . Run ( )
69
+ rawError = runCmdContext ( ctx , cmd )
69
70
friendlyError = rawError
70
71
if rawError != nil {
71
72
// Check for 'go' executable not being found.
@@ -80,6 +81,34 @@ func (i *Invocation) RunRaw(ctx context.Context) (stdout *bytes.Buffer, stderr *
80
81
return
81
82
}
82
83
84
+ // runCmdContext is like exec.CommandContext except it sends os.Interrupt
85
+ // before os.Kill.
86
+ func runCmdContext (ctx context.Context , cmd * exec.Cmd ) error {
87
+ if err := cmd .Start (); err != nil {
88
+ return err
89
+ }
90
+ resChan := make (chan error , 1 )
91
+ go func () {
92
+ resChan <- cmd .Wait ()
93
+ }()
94
+
95
+ select {
96
+ case err := <- resChan :
97
+ return err
98
+ case <- ctx .Done ():
99
+ }
100
+ // Cancelled. Interrupt and see if it ends voluntarily.
101
+ cmd .Process .Signal (os .Interrupt )
102
+ select {
103
+ case err := <- resChan :
104
+ return err
105
+ case <- time .After (time .Second ):
106
+ }
107
+ // Didn't shut down in response to interrupt. Kill it hard.
108
+ cmd .Process .Kill ()
109
+ return <- resChan
110
+ }
111
+
83
112
func cmdDebugStr (cmd * exec.Cmd ) string {
84
113
env := make (map [string ]string )
85
114
for _ , kv := range cmd .Env {
0 commit comments