Skip to content

Instantly share code, notes, and snippets.

@matti
Created May 4, 2020 05:59
Show Gist options
  • Save matti/dc88cd6c088b3e8755cb7b3fdc20a87e to your computer and use it in GitHub Desktop.
Save matti/dc88cd6c088b3e8755cb7b3fdc20a87e to your computer and use it in GitHub Desktop.
package main
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
)
type stevari struct {
running bool
runChannel chan string
cmd *exec.Cmd
}
func signalHandler(ctx context.Context, cancel context.CancelFunc, stevari *stevari) {
c := make(chan os.Signal, 100)
signal.Notify(c)
for {
select {
case signal := <-c:
switch signal {
case syscall.SIGINT:
if stevari.running {
stevari.running = false
stevari.runChannel <- "term"
} else {
cancel()
}
default:
fmt.Println(signal)
}
}
}
}
func run(ctx context.Context, cancel context.CancelFunc, s *stevari, args []string) {
s.cmd = exec.CommandContext(ctx, args[0], args[1:]...)
s.cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
stdoutPipe, _ := s.cmd.StdoutPipe()
stderrPipe, _ := s.cmd.StderrPipe()
go func() {
io.Copy(os.Stdout, stdoutPipe)
}()
go func() {
io.Copy(os.Stderr, stderrPipe)
}()
defer func() {
if stdoutPipe != nil {
stdoutPipe.Close()
}
if stderrPipe != nil {
stderrPipe.Close()
}
}()
err := s.cmd.Start()
if err != nil {
panic(err.Error())
}
exited := make(chan bool)
go func() {
s.cmd.Wait()
exited <- true
cancel()
}()
for {
select {
case <-exited:
return
case <-ctx.Done():
// TODO: panic: runtime error: invalid memory address or nil pointer dereference
//if ! s.cmd.ProcessState.Exited() {
if s.cmd.ProcessState.ExitCode() < 0 {
s.cmd.Process.Signal(syscall.SIGTERM)
}
select {
case <-exited:
case <-time.After(5 * time.Second):
s.cmd.Process.Signal(syscall.SIGKILL)
s.cmd.Wait()
}
return
case message := <-s.runChannel:
switch message {
case "pause":
s.cmd.Process.Signal(syscall.SIGTSTP)
case "continue":
s.cmd.Process.Signal(syscall.SIGCONT)
case "term":
cancel()
}
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
argsWithoutProg := os.Args[1:]
stevari := stevari{
running: true,
runChannel: make(chan string),
cmd: nil,
}
go signalHandler(ctx, cancel, &stevari)
go func() {
for {
runCtx, runCancel := context.WithCancel(ctx)
go run(runCtx, runCancel, &stevari, argsWithoutProg)
<-runCtx.Done()
fmt.Println("")
fmt.Println("exitcode", stevari.cmd.ProcessState.ExitCode())
time.Sleep(200 * time.Millisecond)
stevari.running = true
}
}()
<-ctx.Done()
fmt.Println("bye.")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment