From 185ddf554a047f02bdc2363f32ff7204a448496d Mon Sep 17 00:00:00 2001 From: Songmu Date: Sun, 7 Jan 2018 01:51:32 +0900 Subject: [PATCH 1/8] refactor --- timeout.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/timeout.go b/timeout.go index 53a0f81..7c9964e 100644 --- a/timeout.go +++ b/timeout.go @@ -55,8 +55,8 @@ func (err *Error) Error() string { // ExitStatus stores exit information of the command type ExitStatus struct { - Code int - typ exitType + Code int + typ exitType } // IsTimedOut returns the command timed out or not @@ -189,8 +189,8 @@ func (tio *Timeout) handleTimeout() (ex ExitStatus) { cmd := tio.getCmd() exitChan := getExitChan(cmd) select { - case exitCode := <-exitChan: - ex.Code = exitCode + case st := <-exitChan: + ex.Code = wrapcommander.WaitStatusToExitCode(st) ex.typ = exitTypeNormal return ex case <-time.After(tio.Duration): @@ -200,7 +200,8 @@ func (tio *Timeout) handleTimeout() (ex ExitStatus) { if tio.KillAfter > 0 { select { - case ex.Code = <-exitChan: + case st := <-exitChan: + ex.Code = wrapcommander.WaitStatusToExitCode(st) case <-time.After(tio.KillAfter): tio.killall() // just to make sure @@ -209,17 +210,19 @@ func (tio *Timeout) handleTimeout() (ex ExitStatus) { ex.typ = exitTypeKilled } } else { - ex.Code = <-exitChan + st := <-exitChan + ex.Code = wrapcommander.WaitStatusToExitCode(st) } return ex } -func getExitChan(cmd *exec.Cmd) chan int { - ch := make(chan int) +func getExitChan(cmd *exec.Cmd) chan syscall.WaitStatus { + ch := make(chan syscall.WaitStatus) go func() { err := cmd.Wait() - ch <- wrapcommander.ResolveExitCode(err) + st, _ := wrapcommander.ErrorToWaitStatus(err) + ch <- st }() return ch } From 99ed9ca484774644cb8321fc152903912f36a069 Mon Sep 17 00:00:00 2001 From: Songmu Date: Sun, 7 Jan 2018 02:21:32 +0900 Subject: [PATCH 2/8] use reflect.SelectCase for simplify code. (but it's magical...) --- timeout.go | 49 +++++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/timeout.go b/timeout.go index 7c9964e..f640ec4 100644 --- a/timeout.go +++ b/timeout.go @@ -7,6 +7,7 @@ import ( "io" "os" "os/exec" + "reflect" "runtime" "syscall" @@ -55,8 +56,8 @@ func (err *Error) Error() string { // ExitStatus stores exit information of the command type ExitStatus struct { - Code int - typ exitType + Code int + typ exitType } // IsTimedOut returns the command timed out or not @@ -90,7 +91,7 @@ type exitType int // exit types const ( - exitTypeNormal exitType = iota + 1 + exitTypeNormal exitType = iota exitTypeTimedOut exitTypeKilled ) @@ -188,32 +189,40 @@ func (tio *Timeout) RunCommand() (chan ExitStatus, error) { func (tio *Timeout) handleTimeout() (ex ExitStatus) { cmd := tio.getCmd() exitChan := getExitChan(cmd) - select { - case st := <-exitChan: - ex.Code = wrapcommander.WaitStatusToExitCode(st) - ex.typ = exitTypeNormal - return ex - case <-time.After(tio.Duration): - tio.terminate() - ex.typ = exitTypeTimedOut + cases := []reflect.SelectCase{ + { // 0: command exit + Chan: reflect.ValueOf(exitChan), + Dir: reflect.SelectRecv, + }, + { // 1: timed out and send signal + Chan: reflect.ValueOf(time.After(tio.Duration)), + Dir: reflect.SelectRecv, + }, } - if tio.KillAfter > 0 { - select { - case st := <-exitChan: + // 2: send KILL signal + cases = append(cases, reflect.SelectCase{ + Chan: reflect.ValueOf(time.After(tio.Duration + tio.KillAfter)), + Dir: reflect.SelectRecv, + }) + } + for { + chosen, recv, _ := reflect.Select(cases) + switch chosen { + case 0: + st := recv.Interface().(syscall.WaitStatus) ex.Code = wrapcommander.WaitStatusToExitCode(st) - case <-time.After(tio.KillAfter): + return ex + case 1: + tio.terminate() + ex.typ = exitTypeTimedOut + case 2: tio.killall() // just to make sure cmd.Process.Kill() - ex.Code = exitKilled ex.typ = exitTypeKilled } - } else { - st := <-exitChan - ex.Code = wrapcommander.WaitStatusToExitCode(st) } - return ex } From 92f5bb07db2c00bff3764767ce6f6cd02906792f Mon Sep 17 00:00:00 2001 From: Songmu Date: Sun, 7 Jan 2018 02:28:24 +0900 Subject: [PATCH 3/8] add Signaled field to ExitStatus struct --- timeout.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/timeout.go b/timeout.go index f640ec4..a1c4dc2 100644 --- a/timeout.go +++ b/timeout.go @@ -56,8 +56,9 @@ func (err *Error) Error() string { // ExitStatus stores exit information of the command type ExitStatus struct { - Code int - typ exitType + Code int + Signaled bool + typ exitType } // IsTimedOut returns the command timed out or not @@ -210,8 +211,10 @@ func (tio *Timeout) handleTimeout() (ex ExitStatus) { chosen, recv, _ := reflect.Select(cases) switch chosen { case 0: - st := recv.Interface().(syscall.WaitStatus) - ex.Code = wrapcommander.WaitStatusToExitCode(st) + if st, ok := recv.Interface().(syscall.WaitStatus); ok { + ex.Code = wrapcommander.WaitStatusToExitCode(st) + ex.Signaled = st.Signaled() + } return ex case 1: tio.terminate() From f5f101393530bbcb716d053c48c1a676b66ff283 Mon Sep 17 00:00:00 2001 From: Songmu Date: Sun, 7 Jan 2018 02:34:05 +0900 Subject: [PATCH 4/8] fix lint --- timeout.go | 1 - 1 file changed, 1 deletion(-) diff --git a/timeout.go b/timeout.go index a1c4dc2..bbeada4 100644 --- a/timeout.go +++ b/timeout.go @@ -226,7 +226,6 @@ func (tio *Timeout) handleTimeout() (ex ExitStatus) { ex.typ = exitTypeKilled } } - return ex } func getExitChan(cmd *exec.Cmd) chan syscall.WaitStatus { From cafcd3caffce80c08165245cb9a5d6476ce04441 Mon Sep 17 00:00:00 2001 From: Songmu Date: Sun, 7 Jan 2018 02:41:13 +0900 Subject: [PATCH 5/8] fix test on windows --- timeout_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timeout_test.go b/timeout_test.go index 1958a9b..0cd54a8 100644 --- a/timeout_test.go +++ b/timeout_test.go @@ -89,7 +89,7 @@ func TestKillAfter(t *testing.T) { Duration: 1 * time.Second, KillAfter: 1 * time.Second, } - exit := tio.RunSimple(true) + exit := tio.RunSimple(false) if exit != 137 { t.Errorf("something wrong: %v", exit) From 700b2da0ce49244f2867154a7ccd432a8a9befd5 Mon Sep 17 00:00:00 2001 From: Songmu Date: Sun, 7 Jan 2018 02:56:26 +0900 Subject: [PATCH 6/8] adjust .travis.yml --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a06f3c2..3629ac4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: go go: -- 1.7 -- 1.8 - tip script: - make lint From 2053ef5a90bbe8fc3bb0fe6a01ec51f6e76802a6 Mon Sep 17 00:00:00 2001 From: Songmu Date: Sun, 7 Jan 2018 03:06:03 +0900 Subject: [PATCH 7/8] ignore signal --- testdata/ignore_sigterm_with_exit3.pl | 10 ++++++++++ timeout_test.go | 13 +++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 testdata/ignore_sigterm_with_exit3.pl diff --git a/testdata/ignore_sigterm_with_exit3.pl b/testdata/ignore_sigterm_with_exit3.pl new file mode 100644 index 0000000..65950da --- /dev/null +++ b/testdata/ignore_sigterm_with_exit3.pl @@ -0,0 +1,10 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +$SIG{TERM} = sub { + exit 3 +}; +sleep 3; + + diff --git a/timeout_test.go b/timeout_test.go index 0cd54a8..21889ec 100644 --- a/timeout_test.go +++ b/timeout_test.go @@ -110,6 +110,19 @@ func TestKillAfterNotKilled(t *testing.T) { } } +func TestIgnoreSignal(t *testing.T) { + tio := &Timeout{ + Cmd: exec.Command("perl", "testdata/ignore_sigterm_with_exit3.pl"), + Signal: syscall.SIGTERM, + Duration: 1 * time.Second, + } + exit := tio.RunSimple(true) + + if exit != 3 { + t.Errorf("something wrong: %v", exit) + } +} + func TestCommandCannotBeInvoked(t *testing.T) { if runtime.GOOS == "windows" { // TODO cmd return 125 for this case From 329f1dab3edca03f2ada98f96745858b69c955aa Mon Sep 17 00:00:00 2001 From: Songmu Date: Sun, 7 Jan 2018 03:07:54 +0900 Subject: [PATCH 8/8] skip test on win --- timeout_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/timeout_test.go b/timeout_test.go index 21889ec..b70a965 100644 --- a/timeout_test.go +++ b/timeout_test.go @@ -111,6 +111,9 @@ func TestKillAfterNotKilled(t *testing.T) { } func TestIgnoreSignal(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skipf("skip test on Windows") + } tio := &Timeout{ Cmd: exec.Command("perl", "testdata/ignore_sigterm_with_exit3.pl"), Signal: syscall.SIGTERM,