Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forcefully terminate all processes with a configured shutdown timeout #240

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 46 additions & 5 deletions src/app/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ type Process struct {
stdin io.WriteCloser
passProvided bool
isTuiEnabled bool
canceller context.CancelFunc
waitDone chan struct{}
}

func NewProcess(opts ...ProcOpts) *Process {
Expand All @@ -85,6 +87,7 @@ func NewProcess(opts ...ProcOpts) *Process {
started: false,
done: false,
procStateChan: make(chan string, 1),
waitDone: make(chan struct{}, 1),
}

for _, opt := range opts {
Expand Down Expand Up @@ -113,6 +116,10 @@ func (p *Process) run() int {

p.onProcessStart()
for {
if len(p.waitDone) > 0 {
<-p.waitDone
}

err := p.setStateAndRun(p.getStartingStateName(), p.getProcessStarter())
if err != nil {
log.Error().Err(err).Msgf(`Failed to run command ["%v"] for process %s`, strings.Join(p.getCommand(), `" "`), p.getName())
Expand All @@ -137,6 +144,8 @@ func (p *Process) run() int {
//TODO Fix this
time.Sleep(50 * time.Millisecond)
_ = p.command.Wait()
p.waitDone <- struct{}{}

p.Lock()
p.setExitCode(p.command.ExitCode())
p.Unlock()
Expand Down Expand Up @@ -202,18 +211,50 @@ func (p *Process) getProcessStarter() func() error {
}

func (p *Process) getCommander() command.Commander {
ctx, canceller := context.WithCancel(context.Background())
p.canceller = canceller
onCancel := func() error {
err := p.command.Stop(
p.procConf.ShutDownParams.Signal,
p.procConf.ShutDownParams.ParentOnly,
)

if err != nil {
return err
}

timeoutInt := p.procConf.ShutDownParams.ShutDownTimeout
if timeoutInt != UndefinedShutdownTimeoutSec {
timeout := time.Duration(timeoutInt) * time.Second
select {
case <-p.waitDone:
break
case <-time.After(timeout):
return p.command.Stop(
int(syscall.SIGKILL),
p.procConf.ShutDownParams.ParentOnly,
)
}
}

return nil
}

if p.procConf.IsTty && !p.isMain {
return command.BuildPtyCommand(
return command.BuildPtyCommandContext(
ctx,
onCancel,
p.procConf.Executable,
p.mergeExtraArgs(),
)
} else {
return command.BuildCommand(
return command.BuildCommandContext(
ctx,
onCancel,
p.procConf.Executable,
p.mergeExtraArgs(),
)
}

}

func (p *Process) mergeExtraArgs() []string {
Expand Down Expand Up @@ -364,8 +405,8 @@ func (p *Process) stopProcess(cancelReadinessFuncs bool) error {
if isStringDefined(p.procConf.ShutDownParams.ShutDownCommand) {
return p.doConfiguredStop(p.procConf.ShutDownParams)
}

return p.command.Stop(p.procConf.ShutDownParams.Signal, p.procConf.ShutDownParams.ParentOnly)
p.canceller()
return nil
}

func (p *Process) doConfiguredStop(params types.ShutDownParams) error {
Expand Down
69 changes: 69 additions & 0 deletions src/app/system_unix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//go:build !windows

package app

import (
"github.com/f1bonacc1/process-compose/src/command"
"github.com/f1bonacc1/process-compose/src/types"
"syscall"
"testing"
"time"
)

func assertProcessStatus(t *testing.T, runner *ProjectRunner, procName string, wantStatus string) {
t.Helper()
state, err := runner.GetProcessState(procName)
if err != nil {
t.Fatalf("%s", err)
}
if state.Status != wantStatus {
t.Fatalf("process %s status want %s got %s", procName, wantStatus, state.Status)
}
}

func TestSystem_TestProcShutDownWithConfiguredTimeOut(t *testing.T) {
ignoresSigTerm := "IgnoresSIGTERM"
shell := command.DefaultShellConfig()
timeout := 5

project := &types.Project{
Processes: map[string]types.ProcessConfig{
ignoresSigTerm: {
Name: ignoresSigTerm,
ReplicaName: ignoresSigTerm,
Executable: shell.ShellCommand,
Args: []string{shell.ShellArgument, "trap '' SIGTERM && sleep 60"},
ShutDownParams: types.ShutDownParams{
ShutDownTimeout: timeout,
Signal: int(syscall.SIGTERM),
},
},
},
ShellConfig: shell,
}
runner, err := NewProjectRunner(&ProjectOpts{project: project})
if err != nil {
t.Fatalf("%s", err)
}
go runner.Run()

time.Sleep(100 * time.Millisecond)
assertProcessStatus(t, runner, ignoresSigTerm, types.ProcessStateRunning)

// If the test fails, cleanup after ourselves
proc := runner.getRunningProcess(ignoresSigTerm)
defer proc.command.Stop(int(syscall.SIGKILL), true)

err = runner.StopProcess(ignoresSigTerm)
if err != nil {
t.Fatalf("%s", err)
}

for i := 0; i < timeout-1; i++ {
time.Sleep(time.Second)
assertProcessStatus(t, runner, ignoresSigTerm, types.ProcessStateTerminating)
}

time.Sleep(2 * time.Second)
assertProcessStatus(t, runner, ignoresSigTerm, types.ProcessStateCompleted)
}
18 changes: 6 additions & 12 deletions src/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,15 @@ import (
"runtime"
)

func BuildCommand(cmd string, args []string) *CmdWrapper {
return &CmdWrapper{
cmd: exec.Command(cmd, args...),
}
func BuildCommandContext(ctx context.Context, onCancel func() error, cmd string, args []string) *CmdWrapper {
cmdCtx := exec.CommandContext(ctx, cmd, args...)
cmdCtx.Cancel = onCancel
return &CmdWrapper{cmd: cmdCtx}
}

func BuildPtyCommand(cmd string, args []string) *CmdWrapperPty {
func BuildPtyCommandContext(ctx context.Context, onCancel func() error, cmd string, args []string) *CmdWrapperPty {
return &CmdWrapperPty{
CmdWrapper: BuildCommand(cmd, args),
}
}

func BuildCommandContext(ctx context.Context, shellCmd string) *CmdWrapper {
return &CmdWrapper{
cmd: exec.CommandContext(ctx, getRunnerShell(), getRunnerArg(), shellCmd),
CmdWrapper: BuildCommandContext(ctx, onCancel, cmd, args),
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/health/exec_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ func (c *execChecker) Status() (interface{}, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(c.timeout)*time.Second)
defer cancel()

cmd := command.BuildCommandContext(ctx, c.command)
cmd := command.BuildCommandShellArgContext(
ctx,
*command.DefaultShellConfig(),
c.command,
)
cmd.SetDir(c.workingDir)

if err := cmd.Run(); err != nil {
Expand Down
Loading