diff --git a/lnd.go b/lnd.go index b05bf6e4..0561cdd3 100644 --- a/lnd.go +++ b/lnd.go @@ -35,7 +35,6 @@ import ( proxy "github.com/grpc-ecosystem/grpc-gateway/runtime" flags "github.com/jessevdk/go-flags" - "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lncfg" @@ -44,6 +43,7 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/btcwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/macaroons" + "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/walletunlocker" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/wire" @@ -62,7 +62,6 @@ var ( Commit string cfg *config - shutdownChannel = make(chan struct{}) registeredChains = newChainRegistry() macaroonDatabaseDir string @@ -94,6 +93,8 @@ var ( // defers created in the top-level scope of a main method aren't executed if // os.Exit() is called. func lndMain() error { + defer ltndLog.Info("Shutdown complete") + // Load the configuration, and parse any command line options. This // function will also set up logging properly. loadedConfig, err := loadConfig() @@ -500,6 +501,7 @@ func lndMain() error { return err } server.fundingMgr = fundingMgr + defer fundingMgr.Stop() // Check macaroon authentication if macaroons aren't disabled. if macaroonService != nil { @@ -517,6 +519,7 @@ func lndMain() error { if err := rpcServer.Start(); err != nil { return err } + defer rpcServer.Stop() grpcServer := grpc.NewServer(serverOpts...) lnrpc.RegisterLightningServer(grpcServer, rpcServer) @@ -574,18 +577,9 @@ func lndMain() error { ltndLog.Infof("Waiting for chain backend to finish sync, "+ "start_height=%v", bestHeight) - // We'll add an interrupt handler in order to process shutdown - // requests while the chain backend syncs. - addInterruptHandler(func() { - rpcServer.Stop() - fundingMgr.Stop() - }) - for { - select { - case <-shutdownChannel: + if !signal.Alive() { return nil - default: } synced, _, err := activeChainControl.wallet.IsSynced() @@ -615,10 +609,10 @@ func lndMain() error { srvrLog.Errorf("unable to start server: %v\n", err) return err } + defer server.Stop() // Now that the server has started, if the autopilot mode is currently // active, then we'll initialize a fresh instance of it and start it. - var pilot *autopilot.Agent if cfg.Autopilot.Active { pilot, err := initAutoPilot(server, cfg.Autopilot) if err != nil { @@ -631,21 +625,12 @@ func lndMain() error { err) return err } + defer pilot.Stop() } - addInterruptHandler(func() { - rpcServer.Stop() - fundingMgr.Stop() - if pilot != nil { - pilot.Stop() - } - server.Stop() - }) - // Wait for shutdown signal from either a graceful server stop or from // the interrupt handler. - <-shutdownChannel - ltndLog.Info("Shutdown complete") + <-signal.ShutdownChannel() return nil } @@ -1036,7 +1021,7 @@ func waitForWalletPassword(grpcEndpoints, restEndpoints []net.Addr, // Don't leave the file open in case the new wallet // could not be created for whatever reason. if err := loader.UnloadWallet(); err != nil { - ltndLog.Errorf("Could not unload new " + + ltndLog.Errorf("Could not unload new "+ "wallet: %v", err) } return nil, err @@ -1061,7 +1046,7 @@ func waitForWalletPassword(grpcEndpoints, restEndpoints []net.Addr, } return walletInitParams, nil - case <-shutdownChannel: + case <-signal.ShutdownChannel(): return nil, fmt.Errorf("shutting down") } } diff --git a/log.go b/log.go index 6dca3907..150fb296 100644 --- a/log.go +++ b/log.go @@ -20,6 +20,7 @@ import ( "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/routing" + "github.com/lightningnetwork/lnd/signal" "github.com/roasbeef/btcd/connmgr" ) @@ -89,6 +90,7 @@ func init() { autopilot.UseLogger(atplLog) contractcourt.UseLogger(cnctLog) sphinx.UseLogger(sphxLog) + signal.UseLogger(ltndLog) } // subsystemLoggers maps each subsystem identifier to its associated logger. diff --git a/rpcserver.go b/rpcserver.go index c58ed0a0..2281448e 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -26,6 +26,7 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" + "github.com/lightningnetwork/lnd/signal" "github.com/lightningnetwork/lnd/zpay32" "github.com/roasbeef/btcd/blockchain" "github.com/roasbeef/btcd/btcec" @@ -3416,7 +3417,7 @@ func (r *rpcServer) GetNetworkInfo(ctx context.Context, func (r *rpcServer) StopDaemon(ctx context.Context, _ *lnrpc.StopRequest) (*lnrpc.StopResponse, error) { - shutdownRequestChannel <- struct{}{} + signal.RequestShutdown() return &lnrpc.StopResponse{}, nil } diff --git a/signal.go b/signal.go deleted file mode 100644 index c409590b..00000000 --- a/signal.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2013-2017 The btcsuite developers -// Copyright (c) 2015-2016 The Decred developers -// Heavily inspired by https://github.com/btcsuite/btcd/blob/master/signal.go -// Copyright (C) 2015-2017 The Lightning Network Developers - -package main - -import ( - "os" - "os/signal" -) - -// interruptChannel is used to receive SIGINT (Ctrl+C) signals. -var interruptChannel chan os.Signal - -// shutdownRequestChannel is used to request the daemon to shutdown gracefully, -// similar to when receiving SIGINT. -var shutdownRequestChannel = make(chan struct{}) - -// addHandlerChannel is used to add an interrupt handler to the list of handlers -// to be invoked on SIGINT (Ctrl+C) signals and shutdown. -var addHandlerChannel = make(chan func()) - -// mainInterruptHandler listens for SIGINT (Ctrl+C) signals on the -// interruptChannel and shutdown requests on the shutdownRequestChannel, and -// invokes the registered interruptCallbacks accordingly. It also listens for -// callback registration. -// It must be run as a goroutine. -func mainInterruptHandler() { - // interruptCallbacks is a list of callbacks to invoke when a - // SIGINT (Ctrl+C) or a shutdown request is received. - var interruptCallbacks []func() - - // isShutdown is a flag which is used to indicate whether or not - // the shutdown signal has already been received and hence any future - // attempts to add a new interrupt handler should invoke them - // immediately. - var isShutdown bool - - // shutdown invokes the registered interrupt handlers, then signals the - // shutdownChannel. - shutdown := func() { - // Ignore more than one shutdown signal. - if isShutdown { - ltndLog.Infof("Already shutting down...") - return - } - isShutdown = true - ltndLog.Infof("Shutting down...") - - // Execute the interrupt callbacks in FIFO order. - for _, callback := range interruptCallbacks { - callback() - } - - // Signal the main goroutine to shutdown. - go func() { - shutdownChannel <- struct{}{} - }() - } - - for { - select { - case <-interruptChannel: - ltndLog.Infof("Received SIGINT (Ctrl+C).") - shutdown() - - case <-shutdownRequestChannel: - ltndLog.Infof("Received shutdown request.") - shutdown() - - case handler := <-addHandlerChannel: - // The shutdown signal has already been received, so - // just invoke any new handlers immediately. - if isShutdown { - handler() - } - - interruptCallbacks = append(interruptCallbacks, handler) - } - } -} - -// addInterruptHandler adds a handler to call when a SIGINT (Ctrl+C) or a -// shutdown request is received. -func addInterruptHandler(handler func()) { - // Create the channel and start the main interrupt handler which invokes - // all other callbacks and exits if not already done. - if interruptChannel == nil { - interruptChannel = make(chan os.Signal, 1) - signal.Notify(interruptChannel, os.Interrupt) - go mainInterruptHandler() - } - - addHandlerChannel <- handler -} diff --git a/signal/log.go b/signal/log.go new file mode 100644 index 00000000..e7034321 --- /dev/null +++ b/signal/log.go @@ -0,0 +1,26 @@ +package signal + +import "github.com/btcsuite/btclog" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + DisableLog() +} + +// DisableLog disables all library log output. Logging output is disabled +// by default until UseLogger is called. +func DisableLog() { + log = btclog.Disabled +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/signal/signal.go b/signal/signal.go new file mode 100644 index 00000000..f1a5b978 --- /dev/null +++ b/signal/signal.go @@ -0,0 +1,101 @@ +// Copyright (c) 2013-2017 The btcsuite developers +// Copyright (c) 2015-2016 The Decred developers +// Heavily inspired by https://github.com/btcsuite/btcd/blob/master/signal.go +// Copyright (C) 2015-2017 The Lightning Network Developers + +package signal + +import ( + "os" + "os/signal" +) + +var ( + // interruptChannel is used to receive SIGINT (Ctrl+C) signals. + interruptChannel = make(chan os.Signal, 1) + + // shutdownRequestChannel is used to request the daemon to shutdown + // gracefully, similar to when receiving SIGINT. + shutdownRequestChannel = make(chan struct{}) + + // quit is closed when instructing the main interrupt handler to exit. + quit = make(chan struct{}) + + // shutdownChannel is closed once the main interrupt handler exits. + shutdownChannel = make(chan struct{}) +) + +func init() { + signal.Notify(interruptChannel, os.Interrupt) + go mainInterruptHandler() +} + +// mainInterruptHandler listens for SIGINT (Ctrl+C) signals on the +// interruptChannel and shutdown requests on the shutdownRequestChannel, and +// invokes the registered interruptCallbacks accordingly. It also listens for +// callback registration. +// It must be run as a goroutine. +func mainInterruptHandler() { + // isShutdown is a flag which is used to indicate whether or not + // the shutdown signal has already been received and hence any future + // attempts to add a new interrupt handler should invoke them + // immediately. + var isShutdown bool + + // shutdown invokes the registered interrupt handlers, then signals the + // shutdownChannel. + shutdown := func() { + // Ignore more than one shutdown signal. + if isShutdown { + log.Infof("Already shutting down...") + return + } + isShutdown = true + log.Infof("Shutting down...") + + // Signal the main interrupt handler to exit, and stop accept + // post-facto requests. + close(quit) + } + + for { + select { + case <-interruptChannel: + log.Infof("Received SIGINT (Ctrl+C).") + shutdown() + + case <-shutdownRequestChannel: + log.Infof("Received shutdown request.") + shutdown() + + case <-quit: + log.Infof("Gracefully shutting down.") + close(shutdownChannel) + return + } + } +} + +// Alive returns true if the main interrupt handler has not been killed. +func Alive() bool { + select { + case <-quit: + return false + default: + return true + } +} + +// RequestShutdown initiates a graceful shutdown from the application. +func RequestShutdown() { + select { + case shutdownRequestChannel <- struct{}{}: + case <-quit: + } +} + +// ShutdownChannel returns the channel that will be closed once the main +// interrupt handler has exited. +func ShutdownChannel() <-chan struct{} { + return shutdownChannel +}