Skip to content

Commit

Permalink
support configurable termination parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
F1bonacc1 committed Jun 22, 2022
1 parent d1b9777 commit abf1145
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
coverage.out
.vscode
bin/**
bin/process*
*.log
.env
# local build output via go build
Expand Down
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,33 @@ process2:
```yaml
process2:
depends_on:
process2:
condition: process_completed_successfully # or "process_started" (default)
process3:
condition: process_completed_successfully # or "process_started" (default)
process3:
condition: process_completed_successfully
```

##### ✅ Termination Parameters

```yaml
process1:
command: "pg_ctl start"
shutdown:
command: "pg_ctl stop"
timeout_seconds: 10 # default 10
signal: 15 # default 15, but only if command is not defined or empty
```

`shutdown` is optional and can be omitted. The default behaviour in this case: `SIGTERM` is issued to the running process.

In case only `shutdown.signal` is defined `[1..31] ` the running process will be terminated with its value.

In case the the `shutdown.command` is defined:

1. The `shutdown.command` is executed with all the Environment Variables of the main process
2. Wait `shutdown.timeout_seconds` for its completion (if not defined wait for 10 seconds)
3. In case of timeout the process will receive the `SIGKILL` signal

#### ✅ <u>Output Handling</u>

##### ✅ Show process name
Expand Down
Empty file added bin/.gitkeep
Empty file.
12 changes: 8 additions & 4 deletions process-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ processes:
restart: "on-failure"
backoff_seconds: 2
depends_on:
process2:
_process2:
condition: process_completed_successfully
process3:
condition: process_completed
Expand All @@ -20,7 +20,7 @@ processes:
environment:
- 'EXIT_CODE=0'

process2:
_process2:
command: "./test_loop.bash process2"
log_location: ./pc.proc2.log
availability:
Expand All @@ -31,7 +31,11 @@ processes:
environment:
- 'ABC=2221'
- 'PRINT_ERR=111'
- 'EXIT_CODE=0'
- 'EXIT_CODE=2'
shutdown:
command: "pkill -f process2"
signal: 15
timeout_seconds: 2

process3:
command: "./test_loop.bash process3"
Expand All @@ -48,7 +52,7 @@ processes:
# restart: on-failure
environment:
- 'ABC=2221'
- 'EXIT_CODE=1'
- 'EXIT_CODE=4'

kcalc:
command: "kcalc"
Expand Down
23 changes: 15 additions & 8 deletions src/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ type Project struct {

type Processes map[string]ProcessConfig
type ProcessConfig struct {
Name string
Disabled bool `yaml:"disabled,omitempty"`
Command string `yaml:"command"`
LogLocation string `yaml:"log_location,omitempty"`
Environment []string `yaml:"environment,omitempty"`
RestartPolicy RestartPolicyConfig `yaml:"availability,omitempty"`
DependsOn DependsOnConfig `yaml:"depends_on,omitempty"`
Extensions map[string]interface{} `yaml:",inline"`
Name string
Disabled bool `yaml:"disabled,omitempty"`
Command string `yaml:"command"`
LogLocation string `yaml:"log_location,omitempty"`
Environment []string `yaml:"environment,omitempty"`
RestartPolicy RestartPolicyConfig `yaml:"availability,omitempty"`
DependsOn DependsOnConfig `yaml:"depends_on,omitempty"`
ShutDownParams ShutDownParams `yaml:"shutdown,omitempty"`
Extensions map[string]interface{} `yaml:",inline"`
}

type ProcessState struct {
Expand Down Expand Up @@ -74,6 +75,12 @@ type RestartPolicyConfig struct {
MaxRestarts int `yaml:"max_restarts,omitempty"`
}

type ShutDownParams struct {
ShutDownCommand string `yaml:"command,omitempty"`
ShutDownTimeout int `yaml:"timeout_seconds,omitempty"`
Signal int `yaml:"signal,omitempty"`
}

const (
// ProcessConditionCompleted is the type for waiting until a process has completed (any exit code).
ProcessConditionCompleted = "process_completed"
Expand Down
33 changes: 33 additions & 0 deletions src/app/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package app

import (
"bufio"
"context"
"fmt"
"io"
"math/rand"
Expand All @@ -10,6 +11,7 @@ import (
"runtime"
"strconv"
"sync"
"syscall"
"time"

"github.com/f1bonacc1/process-compose/src/pclog"
Expand All @@ -18,6 +20,10 @@ import (
"github.com/rs/zerolog/log"
)

const (
DEFAULT_SHUTDOWN_TIMEOUT_SEC = 10
)

type Process struct {
globalEnv []string
procConf ProcessConfig
Expand Down Expand Up @@ -156,6 +162,33 @@ func (p *Process) WontRun() {

}

func (p *Process) shutDown() error {
if isStringDefined(p.procConf.ShutDownParams.ShutDownCommand) {
return p.doConfiguredStop(p.procConf.ShutDownParams)
}
return p.stop(p.procConf.ShutDownParams.Signal)
}

func (p *Process) doConfiguredStop(params ShutDownParams) error {
timeout := params.ShutDownTimeout
if timeout == 0 {
timeout = DEFAULT_SHUTDOWN_TIMEOUT_SEC
}
log.Debug().Msgf("killing %s with timeout %d ...", p.GetName(), timeout)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()

cmd := exec.CommandContext(ctx, getRunnerShell(), getRunnerArg(), params.ShutDownCommand)
cmd.Env = p.getProcessEnvironment()

if err := cmd.Run(); err != nil {
// the process termination timedout and it will be killed
log.Error().Msgf("killing %s with timeout %d failed", p.GetName(), timeout)
return p.stop(int(syscall.SIGKILL))
}
return nil
}

func (p *Process) onProcessEnd() {
if isStringDefined(p.procConf.LogLocation) {
p.logger.Close()
Expand Down
16 changes: 13 additions & 3 deletions src/app/process_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@

package app

import "syscall"
import (
"syscall"
)

func (p *Process) stop() error {
const (
min_sig = 1
max_sig = 31
)

func (p *Process) stop(sig int) error {
if sig < min_sig || sig > max_sig {
sig = int(syscall.SIGTERM)
}
pgid, err := syscall.Getpgid(p.cmd.Process.Pid)
if err == nil {
return syscall.Kill(-pgid, syscall.SIGKILL)
return syscall.Kill(-pgid, syscall.Signal(sig))
}
return err
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/process_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"strconv"
)

func (p *Process) stop() error {
func (p *Process) stop(sig int) error {
//p.cmd.Process.Kill()
kill := exec.Command("TASKKILL", "/T", "/F", "/PID", strconv.Itoa(p.cmd.Process.Pid))
return kill.Run()
Expand Down
2 changes: 1 addition & 1 deletion src/app/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (p *Project) StopProcess(name string) error {
log.Error().Msgf("Process %s is not running", name)
return fmt.Errorf("process %s is not running", name)
}
proc.stop()
proc.shutDown()
return nil
}

Expand Down
7 changes: 6 additions & 1 deletion test_loop.bash
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
#!/usr/bin/env bash


#trap "echo ERROR: The program is terminated ; exit" SIGTERM
trap 'echo CODE: $?; exit $EXIT_CODE' 1 2 3 15

LOOPS=30000
for (( i=1; i<=LOOPS; i++ ))
do
sleep 0.01
#sleep 0.01
sleep 0.5

if [[ -z "${PRINT_ERR}" ]]; then
echo "test loop $i loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop loop $1 $ABC"
Expand Down

0 comments on commit abf1145

Please sign in to comment.