From 816afbf1c28ff01bdda817fb184795ee69058ba9 Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk Date: Mon, 9 Sep 2019 10:08:10 -0400 Subject: [PATCH] Merge PR #5005: Add support for halt-time --- CHANGELOG.md | 8 +++++ baseapp/baseapp.go | 65 ++++++++++++++++++++++++++++++++++------- baseapp/options.go | 11 +++++-- server/config/config.go | 22 +++++++++++--- server/config/toml.go | 18 ++++++++++-- server/start.go | 20 +++++++++++++ 6 files changed, 124 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee65fa66a39e..4e8b39a1b109 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,9 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (cli) [\#4973](https://github.com/cosmos/cosmos-sdk/pull/4973) Enable application CPU profiling via the `--cpu-profile` flag. +* [\#4979](https://github.com/cosmos/cosmos-sdk/issues/4979) Introduce a new `halt-time` config and +CLI option to the `start` command. When provided, an application will halt during `Commit` when the +block time is >= the `halt-time`. ### Improvements @@ -48,6 +51,11 @@ via the `--cpu-profile` flag. provide context and grouping of events based on the messages they correspond to. The `Events` field in `TxResponse` is deprecated and will be removed in the next major release. +### Bug Fixes + +* [\#4979](https://github.com/cosmos/cosmos-sdk/issues/4979) Use `Signal(os.Interrupt)` over +`os.Exit(0)` during configured halting to allow any `defer` calls to be executed. + ## [v0.37.0] - 2019-08-21 ### Bug Fixes diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 582f68436179..14caccbf9743 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -8,6 +8,7 @@ import ( "runtime/debug" "sort" "strings" + "syscall" "errors" @@ -83,9 +84,12 @@ type BaseApp struct { // flag for sealing options and parameters to a BaseApp sealed bool - // height at which to halt the chain and gracefully shutdown + // block height at which to halt the chain and gracefully shutdown haltHeight uint64 + // minimum block time (in Unix seconds) at which to halt the chain and gracefully shutdown + haltTime uint64 + // application's version string appVersion string } @@ -264,8 +268,12 @@ func (app *BaseApp) setMinGasPrices(gasPrices sdk.DecCoins) { app.minGasPrices = gasPrices } -func (app *BaseApp) setHaltHeight(height uint64) { - app.haltHeight = height +func (app *BaseApp) setHaltHeight(haltHeight uint64) { + app.haltHeight = haltHeight +} + +func (app *BaseApp) setHaltTime(haltTime uint64) { + app.haltTime = haltTime } // Router returns the router of the BaseApp. @@ -983,7 +991,27 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc func (app *BaseApp) Commit() (res abci.ResponseCommit) { header := app.deliverState.ctx.BlockHeader() - // write the Deliver state and commit the MultiStore + var halt bool + + switch { + case app.haltHeight > 0 && uint64(header.Height) >= app.haltHeight: + halt = true + + case app.haltTime > 0 && header.Time.Unix() >= int64(app.haltTime): + halt = true + } + + if halt { + app.halt() + + // Note: State is not actually committed when halted. Logs from Tendermint + // can be ignored. + return abci.ResponseCommit{} + } + + // Write the DeliverTx state which is cache-wrapped and commit the MultiStore. + // The write to the DeliverTx state writes all state transitions to the root + // MultiStore (app.cms) so when Commit() is called is persists those values. app.deliverState.ms.Write() commitID := app.cms.Commit() app.logger.Debug("Commit synced", "commit", fmt.Sprintf("%X", commitID)) @@ -997,18 +1025,33 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) { // empty/reset the deliver state app.deliverState = nil - defer func() { - if app.haltHeight > 0 && uint64(header.Height) == app.haltHeight { - app.logger.Info("halting node per configuration", "height", app.haltHeight) - os.Exit(0) - } - }() - return abci.ResponseCommit{ Data: commitID.Hash, } } +// halt attempts to gracefully shutdown the node via SIGINT and SIGTERM falling +// back on os.Exit if both fail. +func (app *BaseApp) halt() { + app.logger.Info("halting node per configuration", "height", app.haltHeight, "time", app.haltTime) + + p, err := os.FindProcess(os.Getpid()) + if err == nil { + // attempt cascading signals in case SIGINT fails (os dependent) + sigIntErr := p.Signal(syscall.SIGINT) + sigTermErr := p.Signal(syscall.SIGTERM) + + if sigIntErr == nil || sigTermErr == nil { + return + } + } + + // Resort to exiting immediately if the process could not be found or killed + // via SIGINT/SIGTERM signals. + app.logger.Info("failed to send SIGINT/SIGTERM; exiting...") + os.Exit(0) +} + // ---------------------------------------------------------------------------- // State diff --git a/baseapp/options.go b/baseapp/options.go index 18ee2fd42c84..9db6f83ee7c2 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -28,9 +28,14 @@ func SetMinGasPrices(gasPricesStr string) func(*BaseApp) { return func(bap *BaseApp) { bap.setMinGasPrices(gasPrices) } } -// SetHaltHeight returns a BaseApp option function that sets the halt height. -func SetHaltHeight(height uint64) func(*BaseApp) { - return func(bap *BaseApp) { bap.setHaltHeight(height) } +// SetHaltHeight returns a BaseApp option function that sets the halt block height. +func SetHaltHeight(blockHeight uint64) func(*BaseApp) { + return func(bap *BaseApp) { bap.setHaltHeight(blockHeight) } +} + +// SetHaltTime returns a BaseApp option function that sets the halt block time. +func SetHaltTime(haltTime uint64) func(*BaseApp) { + return func(bap *BaseApp) { bap.setHaltTime(haltTime) } } func (app *BaseApp) SetName(name string) { diff --git a/server/config/config.go b/server/config/config.go index 1507999345fe..a368df17bb41 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -18,9 +18,23 @@ type BaseConfig struct { // specified in this config (e.g. 0.25token1;0.0001token2). MinGasPrices string `mapstructure:"minimum-gas-prices"` - // HaltHeight contains a non-zero height at which a node will gracefully halt - // and shutdown that can be used to assist upgrades and testing. + // HaltHeight contains a non-zero block height at which a node will gracefully + // halt and shutdown that can be used to assist upgrades and testing. + // + // Note: State will not be committed on the corresponding height and any logs + // indicating such can be safely ignored. HaltHeight uint64 `mapstructure:"halt-height"` + + // HaltTime contains a non-zero minimum block time (in Unix seconds) at which + // a node will gracefully halt and shutdown that can be used to assist + // upgrades and testing. + // + // Note: State will not be committed on the corresponding height and any logs + // indicating such can be safely ignored. + HaltTime uint64 `mapstructure:"halt-time"` + + // InterBlockCache enables inter-block caching. + InterBlockCache bool `mapstructure:"inter-block-cache"` } // Config defines the server's top level configuration @@ -59,8 +73,8 @@ func (c *Config) GetMinGasPrices() sdk.DecCoins { func DefaultConfig() *Config { return &Config{ BaseConfig{ - MinGasPrices: defaultMinGasPrices, - HaltHeight: 0, + MinGasPrices: defaultMinGasPrices, + InterBlockCache: true, }, } } diff --git a/server/config/toml.go b/server/config/toml.go index e381ee204a6f..4034201831ff 100644 --- a/server/config/toml.go +++ b/server/config/toml.go @@ -18,9 +18,23 @@ const defaultConfigTemplate = `# This is a TOML config file. # specified in this config (e.g. 0.25token1;0.0001token2). minimum-gas-prices = "{{ .BaseConfig.MinGasPrices }}" -# HaltHeight contains a non-zero height at which a node will gracefully halt -# and shutdown that can be used to assist upgrades and testing. +# HaltHeight contains a non-zero block height at which a node will gracefully +# halt and shutdown that can be used to assist upgrades and testing. +# +# Note: State will not be committed on the corresponding height and any logs +# indicating such can be safely ignored. halt-height = {{ .BaseConfig.HaltHeight }} + +# HaltTime contains a non-zero minimum block time (in Unix seconds) at which +# a node will gracefully halt and shutdown that can be used to assist upgrades +# and testing. +# +# Note: State will not be committed on the corresponding height and any logs +# indicating such can be safely ignored. +halt-time = {{ .BaseConfig.HaltTime }} + +# InterBlockCache enables inter-block caching. +inter-block-cache = {{ .BaseConfig.InterBlockCache }} ` var configTemplate *template.Template diff --git a/server/start.go b/server/start.go index d3d47df3b827..696163300bdf 100644 --- a/server/start.go +++ b/server/start.go @@ -27,6 +27,7 @@ const ( flagCPUProfile = "cpu-profile" FlagMinGasPrices = "minimum-gas-prices" FlagHaltHeight = "halt-height" + FlagHaltTime = "halt-time" ) // StartCmd runs the service passed in, either stand-alone or in-process with @@ -35,6 +36,24 @@ func StartCmd(ctx *Context, appCreator AppCreator) *cobra.Command { cmd := &cobra.Command{ Use: "start", Short: "Run the full node", + Long: `Run the full node application with Tendermint in or out of process. By +default, the application will run with Tendermint in process. + +Pruning options can be provided via the '--pruning' flag. The options are as follows: + +syncable: only those states not needed for state syncing will be deleted (keeps last 100 + every 10000th) +nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node) +everything: all saved states will be deleted, storing only the current state + +Node halting configurations exist in the form of two flags: '--halt-height' and '--halt-time'. During +the ABCI Commit phase, the node will check if the current block height is greater than or equal to +the halt-height or if the current block time is greater than or equal to the halt-time. If so, the +node will attempt to gracefully shutdown and the block will not be committed. In addition, the node +will not be able to commit subsequent blocks. + +For profiling and benchmarking purposes, CPU profiling can be enabled via the '--cpu-profile' flag +which accepts a path for the resulting pprof file. +`, RunE: func(cmd *cobra.Command, args []string) error { if !viper.GetBool(flagWithTendermint) { ctx.Logger.Info("starting ABCI without Tendermint") @@ -58,6 +77,7 @@ func StartCmd(ctx *Context, appCreator AppCreator) *cobra.Command { "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photino;0.0001stake)", ) cmd.Flags().Uint64(FlagHaltHeight, 0, "Height at which to gracefully halt the chain and shutdown the node") + cmd.Flags().Uint64(FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node") cmd.Flags().String(flagCPUProfile, "", "Enable CPU profiling and write to the provided file") // add support for all Tendermint-specific command line options