diff --git a/main.go b/main.go index e4c75b6..494d15d 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "os/exec" "os/signal" "path/filepath" "syscall" @@ -167,24 +168,40 @@ func runGaper(cfg *Config) error { return fmt.Errorf("watcher error: %v", err) } + var changeRestart bool + go watcher.Watch() for { select { case event := <-watcher.Events: logger.Debug("Detected new changed file: ", event) + changeRestart = true restart(builder, runner) case err := <-watcher.Errors: 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: - logger.Debug("Waiting watch event") time.Sleep(time.Duration(cfg.PollInterval) * time.Millisecond) } } } func restart(builder Builder, runner Runner) error { - if err := runner.Kill(); err != nil { - return fmt.Errorf("kill error: %v", err) + logger.Debug("Restarting program") + + // 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 { @@ -198,6 +215,16 @@ func restart(builder Builder, runner Runner) error { 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) { c := make(chan os.Signal, 2) signal.Notify(c, os.Interrupt, syscall.SIGTERM) diff --git a/runner.go b/runner.go index 1591bfb..b7d049e 100644 --- a/runner.go +++ b/runner.go @@ -16,6 +16,8 @@ const OSWindows = "windows" type Runner interface { Run() (*exec.Cmd, error) Kill() error + Errors() chan error + Exited() bool } type runner struct { @@ -24,6 +26,7 @@ type runner struct { writer io.Writer command *exec.Cmd starttime time.Time + errors chan error } // NewRunner ... @@ -33,6 +36,7 @@ func NewRunner(writer io.Writer, bin string, args []string) Runner { args: args, writer: writer, 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() } +// Errors ... +func (r *runner) Errors() chan error { + return r.errors +} + func (r *runner) runBin() error { r.command = exec.Command(r.bin, r.args...) // nolint gas stdout, err := r.command.StdoutPipe() @@ -113,7 +122,13 @@ func (r *runner) runBin() error { // TODO: handle or log errors go io.Copy(r.writer, stdout) // 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 } diff --git a/testdata/server/main.go b/testdata/server/main.go index a4c49df..1c4e359 100644 --- a/testdata/server/main.go +++ b/testdata/server/main.go @@ -8,10 +8,14 @@ import ( ) 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 }) + http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { + log.Fatal("Forced failure") + }) + log.Println("Starting server") log.Fatal(http.ListenAndServe(":8080", nil)) }