Add support to restart on program exit

This commit is contained in:
Max Claus Nunes 2018-06-19 00:50:05 -03:00
parent 99cc752b38
commit 68c9d713ae
3 changed files with 51 additions and 5 deletions

33
main.go
View File

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"os/exec"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"syscall" "syscall"
@ -167,24 +168,40 @@ func runGaper(cfg *Config) error {
return fmt.Errorf("watcher error: %v", err) return fmt.Errorf("watcher error: %v", err)
} }
var changeRestart bool
go watcher.Watch() go watcher.Watch()
for { for {
select { select {
case event := <-watcher.Events: case event := <-watcher.Events:
logger.Debug("Detected new changed file: ", event) logger.Debug("Detected new changed file: ", event)
changeRestart = true
restart(builder, runner) restart(builder, runner)
case err := <-watcher.Errors: case err := <-watcher.Errors:
return fmt.Errorf("error on watching files: %v", err) return fmt.Errorf("error on watching files: %v", err)
case err := <-runner.Errors():
if changeRestart {
changeRestart = false
} else {
logger.Debug("Detected program exit: ", err)
if err = handleProgramExit(builder, runner, err); err != nil {
return err
}
}
default: default:
logger.Debug("Waiting watch event")
time.Sleep(time.Duration(cfg.PollInterval) * time.Millisecond) time.Sleep(time.Duration(cfg.PollInterval) * time.Millisecond)
} }
} }
} }
func restart(builder Builder, runner Runner) error { func restart(builder Builder, runner Runner) error {
if err := runner.Kill(); err != nil { logger.Debug("Restarting program")
return fmt.Errorf("kill error: %v", err)
// kill process if it is running
if !runner.Exited() {
if err := runner.Kill(); err != nil {
return fmt.Errorf("kill error: %v", err)
}
} }
if err := builder.Build(); err != nil { if err := builder.Build(); err != nil {
@ -198,6 +215,16 @@ func restart(builder Builder, runner Runner) error {
return nil return nil
} }
func handleProgramExit(builder Builder, runner Runner, err error) error {
_, ok := err.(*exec.ExitError)
if !ok {
return fmt.Errorf("couldn't handle program crash restart: %v", err)
}
restart(builder, runner)
return nil
}
func shutdown(runner Runner) { func shutdown(runner Runner) {
c := make(chan os.Signal, 2) c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(c, os.Interrupt, syscall.SIGTERM)

View File

@ -16,6 +16,8 @@ const OSWindows = "windows"
type Runner interface { type Runner interface {
Run() (*exec.Cmd, error) Run() (*exec.Cmd, error)
Kill() error Kill() error
Errors() chan error
Exited() bool
} }
type runner struct { type runner struct {
@ -24,6 +26,7 @@ type runner struct {
writer io.Writer writer io.Writer
command *exec.Cmd command *exec.Cmd
starttime time.Time starttime time.Time
errors chan error
} }
// NewRunner ... // NewRunner ...
@ -33,6 +36,7 @@ func NewRunner(writer io.Writer, bin string, args []string) Runner {
args: args, args: args,
writer: writer, writer: writer,
starttime: time.Now(), starttime: time.Now(),
errors: make(chan error),
} }
} }
@ -91,6 +95,11 @@ func (r *runner) Exited() bool {
return r.command != nil && r.command.ProcessState != nil && r.command.ProcessState.Exited() return r.command != nil && r.command.ProcessState != nil && r.command.ProcessState.Exited()
} }
// Errors ...
func (r *runner) Errors() chan error {
return r.errors
}
func (r *runner) runBin() error { func (r *runner) runBin() error {
r.command = exec.Command(r.bin, r.args...) // nolint gas r.command = exec.Command(r.bin, r.args...) // nolint gas
stdout, err := r.command.StdoutPipe() stdout, err := r.command.StdoutPipe()
@ -113,7 +122,13 @@ func (r *runner) runBin() error {
// TODO: handle or log errors // TODO: handle or log errors
go io.Copy(r.writer, stdout) // nolint errcheck go io.Copy(r.writer, stdout) // nolint errcheck
go io.Copy(r.writer, stderr) // nolint errcheck go io.Copy(r.writer, stderr) // nolint errcheck
go r.command.Wait() // nolint errcheck
// wait for exit errors
go func() {
if err := r.command.Wait(); err != nil {
r.errors <- err
}
}()
return nil return nil
} }

View File

@ -8,10 +8,14 @@ import (
) )
func main() { func main() {
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) // nolint gas fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) // nolint gas
}) })
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
log.Fatal("Forced failure")
})
log.Println("Starting server") log.Println("Starting server")
log.Fatal(http.ListenAndServe(":8080", nil)) log.Fatal(http.ListenAndServe(":8080", nil))
} }