Unclean shutdown on SIGINT / SIGTERM (#3308)

* libs/common: TrapSignal accepts logger as a first parameter

 and does not block anymore
* previously it was dumping "captured ..." msg to os.Stdout
* TrapSignal should not be responsible for blocking thread of execution

Refs #3238

* exit with zero (0) code upon receiving SIGTERM/SIGINT

Refs #3238

* fix formatting in docs/app-dev/abci-cli.md

Co-Authored-By: melekes <anton.kalyaev@gmail.com>

* fix formatting in docs/app-dev/abci-cli.md

Co-Authored-By: melekes <anton.kalyaev@gmail.com>
This commit is contained in:
Anton Kaliaev
2019-02-23 19:48:28 +04:00
committed by Ethan Buchman
parent 41f91318e9
commit cdf3a74f48
12 changed files with 103 additions and 86 deletions

View File

@ -12,6 +12,9 @@ Special thanks to external contributors on this release:
* Apps * Apps
* Go API * Go API
- [libs/common] TrapSignal accepts logger as a first parameter and does not block anymore
* previously it was dumping "captured ..." msg to os.Stdout
* TrapSignal should not be responsible for blocking thread of execution
* Blockchain Protocol * Blockchain Protocol
@ -23,6 +26,7 @@ Special thanks to external contributors on this release:
`/num_unconfirmed_txs` and `/unconfirmed_txs` RPC endpoints. `/num_unconfirmed_txs` and `/unconfirmed_txs` RPC endpoints.
### IMPROVEMENTS: ### IMPROVEMENTS:
- [libs/common] \#3238 exit with zero (0) code upon receiving SIGTERM/SIGINT
### BUG FIXES: ### BUG FIXES:

View File

@ -636,9 +636,7 @@ func cmdQuery(cmd *cobra.Command, args []string) error {
} }
func cmdCounter(cmd *cobra.Command, args []string) error { func cmdCounter(cmd *cobra.Command, args []string) error {
app := counter.NewCounterApplication(flagSerial) app := counter.NewCounterApplication(flagSerial)
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
// Start the listener // Start the listener
@ -651,12 +649,14 @@ func cmdCounter(cmd *cobra.Command, args []string) error {
return err return err
} }
// Wait forever // Stop upon receiving SIGTERM or CTRL-C.
cmn.TrapSignal(func() { cmn.TrapSignal(logger, func() {
// Cleanup // Cleanup
srv.Stop() srv.Stop()
}) })
return nil
// Run forever.
select {}
} }
func cmdKVStore(cmd *cobra.Command, args []string) error { func cmdKVStore(cmd *cobra.Command, args []string) error {
@ -681,12 +681,14 @@ func cmdKVStore(cmd *cobra.Command, args []string) error {
return err return err
} }
// Wait forever // Stop upon receiving SIGTERM or CTRL-C.
cmn.TrapSignal(func() { cmn.TrapSignal(logger, func() {
// Cleanup // Cleanup
srv.Stop() srv.Stop()
}) })
return nil
// Run forever.
select {}
} }
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------

View File

@ -54,10 +54,14 @@ func main() {
panic(err) panic(err)
} }
cmn.TrapSignal(func() { // Stop upon receiving SIGTERM or CTRL-C.
cmn.TrapSignal(logger, func() {
err := rs.Stop() err := rs.Stop()
if err != nil { if err != nil {
panic(err) panic(err)
} }
}) })
// Run forever.
select {}
} }

View File

@ -59,6 +59,11 @@ func ensureAddrHasSchemeOrDefaultToTCP(addr string) (string, error) {
} }
func runProxy(cmd *cobra.Command, args []string) error { func runProxy(cmd *cobra.Command, args []string) error {
// Stop upon receiving SIGTERM or CTRL-C.
cmn.TrapSignal(logger, func() {
// TODO: close up shop
})
nodeAddr, err := ensureAddrHasSchemeOrDefaultToTCP(nodeAddr) nodeAddr, err := ensureAddrHasSchemeOrDefaultToTCP(nodeAddr)
if err != nil { if err != nil {
return err return err
@ -86,9 +91,6 @@ func runProxy(cmd *cobra.Command, args []string) error {
return cmn.ErrorWrap(err, "starting proxy") return cmn.ErrorWrap(err, "starting proxy")
} }
cmn.TrapSignal(func() { // Run forever
// TODO: close up shop select {}
})
return nil
} }

View File

@ -2,12 +2,10 @@ package commands
import ( import (
"fmt" "fmt"
"os"
"os/signal"
"syscall"
"github.com/spf13/cobra" "github.com/spf13/cobra"
cmn "github.com/tendermint/tendermint/libs/common"
nm "github.com/tendermint/tendermint/node" nm "github.com/tendermint/tendermint/node"
) )
@ -57,25 +55,19 @@ func NewRunNodeCmd(nodeProvider nm.NodeProvider) *cobra.Command {
return fmt.Errorf("Failed to create node: %v", err) return fmt.Errorf("Failed to create node: %v", err)
} }
// Stop upon receiving SIGTERM or CTRL-C // Stop upon receiving SIGTERM or CTRL-C.
c := make(chan os.Signal, 1) cmn.TrapSignal(logger, func() {
signal.Notify(c, os.Interrupt, syscall.SIGTERM) if n.IsRunning() {
go func() { n.Stop()
for sig := range c {
logger.Error(fmt.Sprintf("captured %v, exiting...", sig))
if n.IsRunning() {
n.Stop()
}
os.Exit(1)
} }
}() })
if err := n.Start(); err != nil { if err := n.Start(); err != nil {
return fmt.Errorf("Failed to start node: %v", err) return fmt.Errorf("Failed to start node: %v", err)
} }
logger.Info("Started node", "nodeInfo", n.Switch().NodeInfo()) logger.Info("Started node", "nodeInfo", n.Switch().NodeInfo())
// Run forever // Run forever.
select {} select {}
}, },
} }

View File

@ -89,12 +89,14 @@ func cmdKVStore(cmd *cobra.Command, args []string) error {
return err return err
} }
// Wait forever // Stop upon receiving SIGTERM or CTRL-C.
cmn.TrapSignal(func() { cmn.TrapSignal(logger, func() {
// Cleanup // Cleanup
srv.Stop() srv.Stop()
}) })
return nil
// Run forever.
select {}
} }
``` ```
@ -238,12 +240,14 @@ func cmdCounter(cmd *cobra.Command, args []string) error {
return err return err
} }
// Wait forever // Stop upon receiving SIGTERM or CTRL-C.
cmn.TrapSignal(func() { cmn.TrapSignal(logger, func() {
// Cleanup // Cleanup
srv.Stop() srv.Stop()
}) })
return nil
// Run forever.
select {}
} }
``` ```

View File

@ -29,7 +29,21 @@ func parseFlags() (headPath string, chopSize int64, limitSize int64, version boo
return return
} }
type fmtLogger struct{}
func (fmtLogger) Info(msg string, keyvals ...interface{}) {
strs := make([]string, len(keyvals))
for i, kv := range keyvals {
strs[i] = fmt.Sprintf("%v", kv)
}
fmt.Printf("%s %s\n", msg, strings.Join(strs, ","))
}
func main() { func main() {
// Stop upon receiving SIGTERM or CTRL-C.
cmn.TrapSignal(fmtLogger{}, func() {
fmt.Println("logjack shutting down")
})
// Read options // Read options
headPath, chopSize, limitSize, version := parseFlags() headPath, chopSize, limitSize, version := parseFlags()
@ -51,29 +65,22 @@ func main() {
os.Exit(1) os.Exit(1)
} }
go func() { // Forever read from stdin and write to AutoFile.
// Forever, read from stdin and write to AutoFile. buf := make([]byte, readBufferSize)
buf := make([]byte, readBufferSize) for {
for { n, err := os.Stdin.Read(buf)
n, err := os.Stdin.Read(buf) group.Write(buf[:n])
group.Write(buf[:n]) group.Flush()
group.Flush() if err != nil {
if err != nil { group.Stop()
group.Stop() if err == io.EOF {
if err == io.EOF { os.Exit(0)
os.Exit(0) } else {
} else { fmt.Println("logjack errored")
fmt.Println("logjack errored") os.Exit(1)
os.Exit(1)
}
} }
} }
}() }
// Trap signal
cmn.TrapSignal(func() {
fmt.Println("logjack shutting down")
})
} }
func parseBytesize(chopSize string) int64 { func parseBytesize(chopSize string) int64 {

View File

@ -34,21 +34,24 @@ func GoPath() string {
return path return path
} }
// TrapSignal catches the SIGTERM and executes cb function. After that it exits type logger interface {
// with code 1. Info(msg string, keyvals ...interface{})
func TrapSignal(cb func()) { }
// TrapSignal catches the SIGTERM/SIGINT and executes cb function. After that it exits
// with code 0.
func TrapSignal(logger logger, cb func()) {
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() { go func() {
for sig := range c { for sig := range c {
fmt.Printf("captured %v, exiting...\n", sig) logger.Info(fmt.Sprintf("captured %v, exiting...", sig))
if cb != nil { if cb != nil {
cb() cb()
} }
os.Exit(1) os.Exit(0)
} }
}() }()
select {}
} }
// Kill the running process by sending itself SIGTERM. // Kill the running process by sending itself SIGTERM.

View File

@ -5,7 +5,7 @@ import (
"testing" "testing"
) )
func TestGoPath(t *testing.T) { func TestOSGoPath(t *testing.T) {
// restore original gopath upon exit // restore original gopath upon exit
path := os.Getenv("GOPATH") path := os.Getenv("GOPATH")
defer func() { defer func() {
@ -28,7 +28,7 @@ func TestGoPath(t *testing.T) {
} }
} }
func TestGoPathWithoutEnvVar(t *testing.T) { func TestOSGoPathWithoutEnvVar(t *testing.T) {
// restore original gopath upon exit // restore original gopath upon exit
path := os.Getenv("GOPATH") path := os.Getenv("GOPATH")
defer func() { defer func() {

View File

@ -24,17 +24,19 @@ type Result struct {
} }
func main() { func main() {
mux := http.NewServeMux() var (
cdc := amino.NewCodec() mux = http.NewServeMux()
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) cdc = amino.NewCodec()
logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout))
)
// Stop upon receiving SIGTERM or CTRL-C.
cmn.TrapSignal(logger, func() {})
rpcserver.RegisterRPCFuncs(mux, routes, cdc, logger) rpcserver.RegisterRPCFuncs(mux, routes, cdc, logger)
listener, err := rpcserver.Listen("0.0.0.0:8008", rpcserver.Config{}) listener, err := rpcserver.Listen("0.0.0.0:8008", rpcserver.Config{})
if err != nil { if err != nil {
cmn.Exit(err.Error()) cmn.Exit(err.Error())
} }
go rpcserver.StartHTTPServer(listener, mux, logger) rpcserver.StartHTTPServer(listener, mux, logger)
// Wait forever
cmn.TrapSignal(func() {
})
} }

View File

@ -4,14 +4,13 @@ import (
"flag" "flag"
"fmt" "fmt"
"os" "os"
"os/signal"
"strings" "strings"
"sync" "sync"
"syscall"
"time" "time"
"github.com/go-kit/kit/log/term" "github.com/go-kit/kit/log/term"
cmn "github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
tmrpc "github.com/tendermint/tendermint/rpc/client" tmrpc "github.com/tendermint/tendermint/rpc/client"
) )
@ -94,18 +93,12 @@ Examples:
"broadcast_tx_"+broadcastTxMethod, "broadcast_tx_"+broadcastTxMethod,
) )
// Quit when interrupted or received SIGTERM. // Stop upon receiving SIGTERM or CTRL-C.
c := make(chan os.Signal, 1) cmn.TrapSignal(logger, func() {
signal.Notify(c, os.Interrupt, syscall.SIGTERM) for _, t := range transacters {
go func() { t.Stop()
for sig := range c {
fmt.Printf("captured %v, exiting...\n", sig)
for _, t := range transacters {
t.Stop()
}
os.Exit(1)
} }
}() })
// Wait until transacters have begun until we get the start time. // Wait until transacters have begun until we get the start time.
timeStart := time.Now() timeStart := time.Now()

View File

@ -58,13 +58,17 @@ Examples:
ton.Start() ton.Start()
} }
cmn.TrapSignal(func() { // Stop upon receiving SIGTERM or CTRL-C.
cmn.TrapSignal(logger, func() {
if !noton { if !noton {
ton.Stop() ton.Stop()
} }
monitor.Stop() monitor.Stop()
listener.Close() listener.Close()
}) })
// Run forever.
select {}
} }
func startMonitor(endpoints string) *monitor.Monitor { func startMonitor(endpoints string) *monitor.Monitor {