From 7c03e3340dd99979975b66e9510e7f50834e705b Mon Sep 17 00:00:00 2001 From: Heschi Kreinick Date: Mon, 24 Feb 2020 16:58:45 -0500 Subject: [PATCH] internal/gocommand: kill gracefully We want the go command to get a chance to do its cleanups. Send it Interrupt before Kill. Fixes golang/go#37368. Change-Id: Id891d2f4f85aae30d2aaa19b9c854d2346ec6e1f Reviewed-on: https://go-review.googlesource.com/c/tools/+/220738 Run-TryBot: Heschi Kreinick Reviewed-by: Rebecca Stambler --- internal/gocommand/invoke.go | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/internal/gocommand/invoke.go b/internal/gocommand/invoke.go index 26cf7b18e3..75d73e744f 100644 --- a/internal/gocommand/invoke.go +++ b/internal/gocommand/invoke.go @@ -5,6 +5,7 @@ import ( "bytes" "context" "fmt" + "os" "os/exec" "strings" "time" @@ -49,7 +50,7 @@ func (i *Invocation) RunRaw(ctx context.Context) (stdout *bytes.Buffer, stderr * goArgs = append(goArgs, i.BuildFlags...) goArgs = append(goArgs, i.Args...) } - cmd := exec.CommandContext(ctx, "go", goArgs...) + cmd := exec.Command("go", goArgs...) stdout = &bytes.Buffer{} stderr = &bytes.Buffer{} cmd.Stdout = stdout @@ -65,7 +66,7 @@ func (i *Invocation) RunRaw(ctx context.Context) (stdout *bytes.Buffer, stderr * defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now()) - rawError = cmd.Run() + rawError = runCmdContext(ctx, cmd) friendlyError = rawError if rawError != nil { // Check for 'go' executable not being found. @@ -80,6 +81,34 @@ func (i *Invocation) RunRaw(ctx context.Context) (stdout *bytes.Buffer, stderr * return } +// runCmdContext is like exec.CommandContext except it sends os.Interrupt +// before os.Kill. +func runCmdContext(ctx context.Context, cmd *exec.Cmd) error { + if err := cmd.Start(); err != nil { + return err + } + resChan := make(chan error, 1) + go func() { + resChan <- cmd.Wait() + }() + + select { + case err := <-resChan: + return err + case <-ctx.Done(): + } + // Cancelled. Interrupt and see if it ends voluntarily. + cmd.Process.Signal(os.Interrupt) + select { + case err := <-resChan: + return err + case <-time.After(time.Second): + } + // Didn't shut down in response to interrupt. Kill it hard. + cmd.Process.Kill() + return <-resChan +} + func cmdDebugStr(cmd *exec.Cmd) string { env := make(map[string]string) for _, kv := range cmd.Env {