// Terminate kills the application func (a *App) Terminate() { a.signalLock.Lock() defer a.signalLock.Unlock() a.stopPolling() a.forAllServices(deregisterService) // Run and wait for preStop command to exit (continues // unconditionally so we don't worry about returned errors here) commands.RunAndWait(a.PreStopCmd, log.Fields{"process": "PreStop"}) if a.Command == nil || a.Command.Cmd == nil || a.Command.Cmd.Process == nil { // Not managing the process, so don't do anything return } cmd := a.Command.Cmd // get the underlying process if a.StopTimeout > 0 { if err := cmd.Process.Signal(syscall.SIGTERM); err != nil { log.Warnf("Error sending SIGTERM to application: %s", err) } else { time.AfterFunc(time.Duration(a.StopTimeout)*time.Second, func() { log.Infof("Killing Process %#v", cmd.Process) cmd.Process.Kill() }) return } } log.Infof("Killing Process %#v", a.Command.Cmd.Process) cmd.Process.Kill() }
// Run starts the application and blocks until finished func (a *App) Run() { // Set up handlers for polling and to accept signal interrupts if 1 == os.Getpid() { reapChildren() } args := getArgs(flag.Args()) cmd, err := commands.NewCommand(args, "0") if err != nil { log.Errorf("Unable to parse command arguments: %v", err) } cmd.Name = "APP" a.Command = cmd a.handleSignals() if a.PreStartCmd != nil { // Run the preStart handler, if any, and exit if it returns an error fields := log.Fields{"process": "PreStart"} if code, err := commands.RunAndWait(a.PreStartCmd, fields); err != nil { os.Exit(code) } } a.handleCoprocesses() a.handlePolling() if a.Command != nil { // Run our main application and capture its stdout/stderr. // This will block until the main application exits and then os.Exit // with the exit code of that application. code, err := commands.RunAndWait(a.Command, nil) if err != nil { log.Println(err) } // Run the PostStop handler, if any, and exit if it returns an error if a.PostStopCmd != nil { fields := log.Fields{"process": "PostStop"} if postStopCode, err := commands.RunAndWait(a.PostStopCmd, fields); err != nil { os.Exit(postStopCode) } } os.Exit(code) } // block forever, as we're polling in the two polling functions and // did not os.Exit by waiting on an external application. select {} }
// Test handler for SIGTERM. Note that the SIGCHLD handler is fired // by this same test, but that we don't have a separate unit test // because they'll interfere with each other's state. func TestTerminateSignal(t *testing.T) { app := getSignalTestConfig() startTime := time.Now() go func() { if exitCode, _ := commands.RunAndWait(app.Command, nil); exitCode != 2 { t.Fatalf("Expected exit code 2 but got %d", exitCode) } }() // we need time for the forked process to start up and this is async runtime.Gosched() time.Sleep(10 * time.Millisecond) app.Terminate() elapsed := time.Since(startTime) if elapsed.Seconds() > float64(app.StopTimeout) { t.Fatalf("Expected elapsed time <= %d seconds, but was %.2f", app.StopTimeout, elapsed.Seconds()) } }
// Start runs the coprocess func (c *Coprocess) Start() { log.Debugf("coprocess[%s].Start", c.Name) fields := log.Fields{"process": "coprocess", "coprocess": c.Name} // always reset restartsRemain when we load the config c.restartsRemain = c.restartLimit for { if c.restartLimit != unlimitedRestarts && c.restartsRemain <= haltRestarts { break } if code, err := commands.RunAndWait(c.cmd, fields); err != nil { log.Errorf("coprocess[%s] exited (%s): %s", c.Name, code, err) } log.Debugf("coprocess[%s] exited", c.Name) if !c.restart { break } c.restartsRemain-- } }