From 3f58719247da65cdd03f3a311510f3a8796a5777 Mon Sep 17 00:00:00 2001 From: Vibhav Bobade Date: Fri, 17 Apr 2020 03:51:23 +0530 Subject: [PATCH] Add Signal Forwarding to Entrypoint Runner The Entrypoint process should be notified and signals received should be propogated as necessary. This signal forwarding mimics the one in https://github.com/pablo-ruth/go-init which is an golang implementation of https://github.com/Yelp/dumb-init . The cmd.Run() has also been replaced with a cmd.Start() and cmd.Wait() to systematically start the command and Wait for it's completion without prematurely exiting. --- cmd/entrypoint/runner.go | 38 ++++++++++++++++++++++++++++++++--- cmd/entrypoint/runner_test.go | 16 +++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 cmd/entrypoint/runner_test.go diff --git a/cmd/entrypoint/runner.go b/cmd/entrypoint/runner.go index 9d89b4d2c19..a8082dfa533 100644 --- a/cmd/entrypoint/runner.go +++ b/cmd/entrypoint/runner.go @@ -3,6 +3,8 @@ package main import ( "os" "os/exec" + "os/signal" + "syscall" "github.com/tektoncd/pipeline/pkg/entrypoint" ) @@ -11,22 +13,52 @@ import ( // stdout/stderr are collected -- needs e2e tests. // realRunner actually runs commands. -type realRunner struct{} +type realRunner struct { + signals chan os.Signal +} var _ entrypoint.Runner = (*realRunner)(nil) -func (*realRunner) Run(args ...string) error { +func (rr *realRunner) Run(args ...string) error { if len(args) == 0 { return nil } name, args := args[0], args[1:] + // Receive system signals on "rr.signals" + if rr.signals == nil { + rr.signals = make(chan os.Signal, 1) + } + defer close(rr.signals) + signal.Notify(rr.signals) + defer signal.Reset() + cmd := exec.Command(name, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr + // dedicated PID group used to forward signals to + // main process and all children + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + // Start defined command + if err := cmd.Start(); err != nil { + return err + } - if err := cmd.Run(); err != nil { + // Goroutine for signals forwarding + go func() { + for s := range rr.signals { + // Forward signal to main process and all children + if s != syscall.SIGCHLD { + _ = syscall.Kill(-cmd.Process.Pid, s.(syscall.Signal)) + } + } + }() + + // Wait for command to exit + if err := cmd.Wait(); err != nil { return err } + return nil } diff --git a/cmd/entrypoint/runner_test.go b/cmd/entrypoint/runner_test.go new file mode 100644 index 00000000000..3ac615b6f33 --- /dev/null +++ b/cmd/entrypoint/runner_test.go @@ -0,0 +1,16 @@ +package main + +import ( + "os" + "syscall" + "testing" +) + +func TestRealRunnerSignalForwarding(t *testing.T) { + rr := realRunner{} + rr.signals = make(chan os.Signal, 1) + rr.signals <- syscall.SIGINT + if err := rr.Run("sleep", "3600"); err.Error() == "signal: interrupt" { + t.Logf("SIGINT forwarded to Entrypoint") + } +}