Skip to content

Commit

Permalink
runner: add ability to wait for a process to finish before moving on
Browse files Browse the repository at this point in the history
  • Loading branch information
ucirello committed Aug 19, 2024
1 parent 871599f commit a4487d1
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 30 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ types will halt and not restart.
- signal (in process types): "SIGTERM", "term", or "15" terminates the process;
"SIGKILL", "kill", or "9" kills the process. The default is "SIGKILL".

- signalWait (in process types): duration to wait after sending the signal to
the process.

- sticky (in build process types): a sticky build is not interrupted when file
changes are detected.

Expand Down
12 changes: 12 additions & 0 deletions internal/procfile/procfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@
// process; "SIGKILL", "kill", or "9" kills the process. The default is
// "SIGKILL".
//
// - signalWait (in process types): duration to wait after sending the signal to
// the process.
//
// - sticky (in build process types): a sticky build is not interrupted when
// file changes are detected.
//
Expand All @@ -72,6 +75,7 @@ import (
"os"
"strconv"
"strings"
"time"

"cirello.io/runner/internal/runner"
)
Expand Down Expand Up @@ -143,6 +147,14 @@ func Parse(r io.Reader) (*runner.Runner, error) {
proc.Signal = runner.ParseSignal(strings.TrimPrefix(part, "signal="))
continue
}
if strings.HasPrefix(part, "signalWait=") {
signalWait, err := time.ParseDuration(strings.TrimPrefix(part, "signalWait="))
if err != nil {
return rnr, err
}
proc.SignalWait = signalWait
continue
}
if strings.HasPrefix(part, "optional=") {
optional, err := strconv.ParseBool(strings.TrimPrefix(part, "optional="))
if err != nil {
Expand Down
28 changes: 18 additions & 10 deletions internal/runner/cmd_others.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,32 @@
package runner

import (
"context"
"fmt"
"os/exec"
"syscall"
"time"
)

func command(cmd string, signal Signal) (*exec.Cmd, func() error) {
c := exec.Command("sh", "-c", cmd)
func command(ctx context.Context, cmd string, signal Signal, signalWait time.Duration) *exec.Cmd {
c := exec.CommandContext(ctx, "sh", "-c", cmd)
c.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
return c, func() error {
if c.Process == nil {
return nil
}
c.Cancel = func() error {
pgid := -c.Process.Pid
kill := syscall.SIGKILL
osSignal := syscall.SIGKILL
if signal == SignalTERM {
kill = syscall.SIGTERM
osSignal = syscall.SIGTERM
}
if err := c.Process.Signal(osSignal); err != nil {
return fmt.Errorf("cannot signal process: %w", err)
}
if err := syscall.Kill(pgid, osSignal); err != nil {
return fmt.Errorf("cannot signal process group: %w", err)
}
if signalWait > 0 {
time.Sleep(signalWait)
}
_ = c.Process.Signal(kill)
_ = syscall.Kill(pgid, kill)
return nil
}
return c
}
13 changes: 3 additions & 10 deletions internal/runner/cmd_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,9 @@ package runner
import (
"context"
"os/exec"
"time"
)

func command(cmd string, signal Signal) (*exec.Cmd, func() error) {
ctx, cancel := context.WithCancel(context.Background())
c := exec.CommandContext(ctx, "cmd", "/c", cmd)
return c, func() error {
// Custom signal is not supported on Windows.
_ = signal
cancel()
_ = c.Wait()
return nil
}
func command(ctx context.Context, cmd string, _ Signal, _ time.Duration) *exec.Cmd {
return exec.CommandContext(ctx, "cmd", "/c", cmd)
}
15 changes: 5 additions & 10 deletions internal/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ type ProcessType struct {
// Signal indicates how a process should be halted.
Signal Signal

// SignalWait indicates how long to wait for a process to be finished
// before releasing it.
SignalWait time.Duration

// Sticky processes are not interrupted by filesystem events.
Sticky bool

Expand Down Expand Up @@ -468,7 +472,7 @@ func (r *Runner) startProcess(ctx context.Context, sv *ProcessType, procCount, p
fmt.Fprintln(pw, "listening on", port)
}
fmt.Fprintln(pw)
c, stopCmd := command(cmd, sv.Signal)
c := command(ctx, cmd, sv.Signal, sv.SignalWait)
c.Dir = r.WorkDir

c.Env = os.Environ()
Expand Down Expand Up @@ -504,15 +508,6 @@ func (r *Runner) startProcess(ctx context.Context, sv *ProcessType, procCount, p
if sv.WaitFor != "" {
r.waitFor(ctx, pw, sv.WaitFor)
}
done := make(chan struct{})
go func() {
select {
case <-ctx.Done():
stopCmd()
case <-done:
}
}()
defer close(done)
if err := c.Run(); err != nil {
fmt.Fprintf(pw, "exec error %s: (%s) %v\n", procName, cmd, err)
return false
Expand Down
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ types will halt and not restart.
- signal (in process types): "SIGTERM", "term", or "15" terminates the process;
"SIGKILL", "kill", or "9" kills the process. The default is "SIGKILL".
- signalWait (in process types): duration to wait after sending the signal to
the process.
- sticky (in build process types): a sticky build is not interrupted when file
changes are detected.
Expand Down

0 comments on commit a4487d1

Please sign in to comment.