diff --git a/config_builder.go b/config_builder.go index afafbed754..ad7f832147 100644 --- a/config_builder.go +++ b/config_builder.go @@ -261,7 +261,7 @@ func (d *DefaultWalletImpl) ValidateMacaroon(ctx context.Context, // Because the default implementation does not return any permissions, // we shouldn't be registered as an external validator at all and this // should never be invoked. - return fmt.Errorf("default implementation does not support external " + + return errors.New("default implementation does not support external " + "macaroon validation") } @@ -365,9 +365,9 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context, if d.cfg.WalletUnlockPasswordFile != "" && !walletExists && !d.cfg.WalletUnlockAllowCreate { - return nil, nil, nil, fmt.Errorf("wallet unlock password file " + - "was specified but wallet does not exist; initialize " + - "the wallet before using auto unlocking") + return nil, nil, nil, errors.New("wallet unlock password " + + "file was specified but wallet does not exist; " + + "initialize the wallet before using auto unlocking") } // What wallet mode are we running in? We've already made sure the no @@ -1099,7 +1099,7 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( if len(invoiceSlice.Invoices) > 0 { cleanUp() - err := fmt.Errorf("found invoices in the KV invoice " + + err := errors.New("found invoices in the KV invoice " + "DB, migration to native SQL is not yet " + "supported") d.logger.Error(err) @@ -1161,7 +1161,7 @@ func (d *DefaultDatabaseBuilder) BuildDatabase( // this RPC server. func waitForWalletPassword(cfg *Config, pwService *walletunlocker.UnlockerService, - loaderOpts []btcwallet.LoaderOption, shutdownChan <-chan struct{}) ( + loaderOpts []btcwallet.LoaderOption, shutdownChan <-chan bool) ( *walletunlocker.WalletUnlockParams, error) { // Wait for user to provide the password. @@ -1234,7 +1234,7 @@ func waitForWalletPassword(cfg *Config, // this case we need to import each of the xpubs individually. case watchOnlyAccounts != nil: if !cfg.RemoteSigner.Enable { - return nil, fmt.Errorf("cannot initialize " + + return nil, errors.New("cannot initialize " + "watch only wallet with remote " + "signer config disabled") } @@ -1254,7 +1254,7 @@ func waitForWalletPassword(cfg *Config, // or the extended key is set so, we shouldn't get here. // The default case is just here for readability and // completeness. - err = fmt.Errorf("cannot create wallet, neither seed " + + err = errors.New("cannot create wallet, neither seed " + "nor extended key was given") } if err != nil { @@ -1311,7 +1311,7 @@ func waitForWalletPassword(cfg *Config, // If we got a shutdown signal we just return with an error immediately case <-shutdownChan: - return nil, fmt.Errorf("shutting down") + return nil, errors.New("shutting down") } } @@ -1378,7 +1378,7 @@ func initNeutrinoBackend(ctx context.Context, cfg *Config, chainDir string, // every other case, the neutrino.validatechannels overwrites the // routing.assumechanvalid value. if cfg.NeutrinoMode.ValidateChannels && cfg.Routing.AssumeChannelValid { - return nil, nil, fmt.Errorf("can't set both " + + return nil, nil, errors.New("can't set both " + "neutrino.validatechannels and routing." + "assumechanvalid to true at the same time") } diff --git a/docs/release-notes/release-notes-0.19.0.md b/docs/release-notes/release-notes-0.19.0.md index 4fc64028d3..1782abb440 100644 --- a/docs/release-notes/release-notes-0.19.0.md +++ b/docs/release-notes/release-notes-0.19.0.md @@ -136,6 +136,10 @@ [here](https://github.com/lightningnetwork/lnd/blob/master/chainio/README.md) to learn more. +* [Shutdown Exit Handling](https://github.com/lightningnetwork/lnd/pull/9395) + is added to manage shutdowns with status codes. Exits with code 1 for critical + errors, and code 0 for normal shutdowns (e.g., from StopDaemon RPC call). + ## RPC Updates * Some RPCs that previously just returned an empty response message now at least diff --git a/lnd.go b/lnd.go index 2b46e83c93..d0d74e758f 100644 --- a/lnd.go +++ b/lnd.go @@ -799,7 +799,11 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg, // Wait for shutdown signal from either a graceful server stop or from // the interrupt handler. - <-interceptor.ShutdownChannel() + normalShutdown := <-interceptor.ShutdownChannel() + if !normalShutdown { + return errors.New("LND shut down with an error") + } + return nil } diff --git a/log.go b/log.go index d3c93b4325..2d80eccbb7 100644 --- a/log.go +++ b/log.go @@ -110,7 +110,7 @@ func genSubLogger(root *build.SubLoggerManager, return } - interceptor.RequestShutdown() + interceptor.RequestShutdown(false) } // Return a function which will create a sublogger from our root diff --git a/rpcserver.go b/rpcserver.go index 72e2fa4afd..860e8652b9 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -7062,7 +7062,7 @@ func (r *rpcServer) StopDaemon(_ context.Context, "shut down, please wait until rescan finishes") } - r.interceptor.RequestShutdown() + r.interceptor.RequestShutdown(true) return &lnrpc.StopResponse{ Status: "shutdown initiated, check logs for progress", diff --git a/signal/signal.go b/signal/signal.go index 8b4a01485c..cf5ae08cb1 100644 --- a/signal/signal.go +++ b/signal/signal.go @@ -100,11 +100,11 @@ type Interceptor struct { interruptChannel chan os.Signal // shutdownChannel is closed once the main interrupt handler exits. - shutdownChannel chan struct{} + shutdownChannel chan bool // shutdownRequestChannel is used to request the daemon to shutdown // gracefully, similar to when receiving SIGINT. - shutdownRequestChannel chan struct{} + shutdownRequestChannel chan bool // quit is closed when instructing the main interrupt handler to exit. // Note that to avoid losing notifications, only shutdown func may @@ -124,8 +124,8 @@ func Intercept() (Interceptor, error) { channels := Interceptor{ interruptChannel: make(chan os.Signal, 1), - shutdownChannel: make(chan struct{}), - shutdownRequestChannel: make(chan struct{}), + shutdownChannel: make(chan bool, 1), + shutdownRequestChannel: make(chan bool), quit: make(chan struct{}), } @@ -171,18 +171,21 @@ func (c *Interceptor) mainInterruptHandler() { close(c.quit) } + var normalShutdown bool for { select { case signal := <-c.interruptChannel: log.Infof("Received %v", signal) + normalShutdown = true shutdown() - case <-c.shutdownRequestChannel: + case normalShutdown = <-c.shutdownRequestChannel: log.Infof("Received shutdown request.") shutdown() case <-c.quit: log.Infof("Gracefully shutting down.") + c.shutdownChannel <- normalShutdown close(c.shutdownChannel) signal.Stop(c.interruptChannel) return @@ -215,15 +218,15 @@ func (c *Interceptor) Alive() bool { } // RequestShutdown initiates a graceful shutdown from the application. -func (c *Interceptor) RequestShutdown() { +func (c *Interceptor) RequestShutdown(normalShutdown bool) { select { - case c.shutdownRequestChannel <- struct{}{}: + case c.shutdownRequestChannel <- normalShutdown: case <-c.quit: } } // ShutdownChannel returns the channel that will be closed once the main // interrupt handler has exited. -func (c *Interceptor) ShutdownChannel() <-chan struct{} { +func (c *Interceptor) ShutdownChannel() <-chan bool { return c.shutdownChannel }