From 55a625bfb88e35971014dbdd1c48528e729fc553 Mon Sep 17 00:00:00 2001 From: Andrew Newdigate Date: Tue, 9 May 2017 06:14:00 +0100 Subject: Use process groups and kill the entire group on context cancel --- internal/helper/command.go | 52 +++++++++++++++++++++++++++++--- internal/helper/command_wrapper_go1.6.go | 44 --------------------------- internal/helper/command_wrapper_go17.go | 14 --------- 3 files changed, 47 insertions(+), 63 deletions(-) delete mode 100644 internal/helper/command_wrapper_go1.6.go delete mode 100644 internal/helper/command_wrapper_go17.go diff --git a/internal/helper/command.go b/internal/helper/command.go index 5b9288757..fd10c5039 100644 --- a/internal/helper/command.go +++ b/internal/helper/command.go @@ -3,6 +3,7 @@ package helper import ( "fmt" "io" + "log" "os" "os/exec" "syscall" @@ -70,6 +71,15 @@ func NewCommand(cmd *exec.Cmd, stdin io.Reader, stdout io.Writer, env ...string) return command, nil } +func cleanUpProcessGroupNoWait(cmd *exec.Cmd) { + process := cmd.Process + if process != nil && process.Pid > 0 { + // Send SIGTERM to the process group of cmd + syscall.Kill(-process.Pid, syscall.SIGTERM) + } + +} + // CleanUpProcessGroup will send a SIGTERM signal to the process group // belonging to the `cmd` process func CleanUpProcessGroup(cmd *exec.Cmd) { @@ -77,11 +87,7 @@ func CleanUpProcessGroup(cmd *exec.Cmd) { return } - process := cmd.Process - if process != nil && process.Pid > 0 { - // Send SIGTERM to the process group of cmd - syscall.Kill(-process.Pid, syscall.SIGTERM) - } + cleanUpProcessGroupNoWait(cmd) // reap our child process cmd.Wait() @@ -101,3 +107,39 @@ func ExitStatus(err error) (int, bool) { return waitStatus.ExitStatus(), true } + +// CommandWrapper ensures that the command is executed within a context, +// and ensures that the process group is terminated with the +func CommandWrapper(ctx context.Context, name string, arg ...string) *exec.Cmd { + command := exec.Command(name, arg...) + + if ctx != nil { + // Create a channel to listen to the command completion + done := make(chan error, 1) + go func() { + done <- command.Wait() + }() + + // Wait for the process to shutdown or the + // context to be complete + go func() { + select { + case <-ctx.Done(): + log.Printf("Context done, killing process") + cleanUpProcessGroupNoWait(command) + + case err := <-done: + if err != nil { + log.Printf("process done with error = %v", err) + } else { + log.Print("process done gracefully without error") + } + cleanUpProcessGroupNoWait(command) + + } + + }() + } + + return command +} diff --git a/internal/helper/command_wrapper_go1.6.go b/internal/helper/command_wrapper_go1.6.go deleted file mode 100644 index c063a9a37..000000000 --- a/internal/helper/command_wrapper_go1.6.go +++ /dev/null @@ -1,44 +0,0 @@ -// +build !go1.7 - -package helper - -import ( - "log" - "os/exec" - - "golang.org/x/net/context" -) - -// CommandWrapper handles context until we compile using Go 1.7 -func CommandWrapper(ctx context.Context, name string, arg ...string) *exec.Cmd { - command := exec.Command(name, arg...) - - if ctx != nil { - // Create a channel to listen to the command completion - done := make(chan error, 1) - go func() { - done <- command.Wait() - }() - - // Wait for the process to shutdown or the - // context to be complete - go func() { - select { - case <-ctx.Done(): - log.Printf("Context done, killing process") - command.Process.Kill() - - case err := <-done: - if err != nil { - log.Printf("process done with error = %v", err) - } else { - log.Print("process done gracefully without error") - } - - } - - }() - } - - return command -} diff --git a/internal/helper/command_wrapper_go17.go b/internal/helper/command_wrapper_go17.go deleted file mode 100644 index c381331b5..000000000 --- a/internal/helper/command_wrapper_go17.go +++ /dev/null @@ -1,14 +0,0 @@ -//+build go1.7 - -package helper - -import ( - "os/exec" - - "golang.org/x/net/context" -) - -// CommandWrapper handles context until we compile using Go 1.7 -func CommandWrapper(ctx context.Context, name string, arg ...string) *exec.Cmd { - return exec.CommandContext(ctx, name, arg...) -} -- cgit v1.2.3