diff --git a/api/api_common.go b/api/api_common.go index a49397247..aac2a61a7 100644 --- a/api/api_common.go +++ b/api/api_common.go @@ -34,6 +34,9 @@ type Common interface { LogList(context.Context) ([]string, error) LogSetLevel(context.Context, string, string) error + + // trigger graceful shutdown + Shutdown(context.Context) error } // Version provides various build-time information diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 5903f8ee0..38fd89279 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -47,6 +47,8 @@ type CommonStruct struct { LogList func(context.Context) ([]string, error) `perm:"write"` LogSetLevel func(context.Context, string, string) error `perm:"write"` + + Shutdown func(context.Context) error `perm:"admin"` } } @@ -293,6 +295,10 @@ func (c *CommonStruct) LogSetLevel(ctx context.Context, group, level string) err return c.Internal.LogSetLevel(ctx, group, level) } +func (c *CommonStruct) Shutdown(ctx context.Context) error { + return c.Internal.Shutdown(ctx) +} + // FullNodeStruct func (c *FullNodeStruct) ClientListImports(ctx context.Context) ([]api.Import, error) { diff --git a/cmd/lotus-seal-worker/main.go b/cmd/lotus-seal-worker/main.go index a2e14a1e6..8271656d0 100644 --- a/cmd/lotus-seal-worker/main.go +++ b/cmd/lotus-seal-worker/main.go @@ -290,7 +290,7 @@ var runCmd = &cli.Command{ go func() { <-ctx.Done() - log.Warn("Shutting down..") + log.Warn("Shutting down...") if err := srv.Shutdown(context.TODO()); err != nil { log.Errorf("shutting down RPC server failed: %s", err) } diff --git a/cmd/lotus-storage-miner/main.go b/cmd/lotus-storage-miner/main.go index 1186cbc28..e03be22f0 100644 --- a/cmd/lotus-storage-miner/main.go +++ b/cmd/lotus-storage-miner/main.go @@ -27,6 +27,7 @@ func main() { initCmd, rewardsCmd, runCmd, + stopCmd, sectorsCmd, storageCmd, setPriceCmd, diff --git a/cmd/lotus-storage-miner/run.go b/cmd/lotus-storage-miner/run.go index 467033f9c..d1e457970 100644 --- a/cmd/lotus-storage-miner/run.go +++ b/cmd/lotus-storage-miner/run.go @@ -100,9 +100,12 @@ var runCmd = &cli.Command{ return xerrors.Errorf("repo at '%s' is not initialized, run 'lotus-storage-miner init' to set it up", storageRepoPath) } + shutdownChan := make(chan struct{}) + var minerapi api.StorageMiner stop, err := node.New(ctx, node.StorageMiner(&minerapi), + node.Override(new(dtypes.ShutdownChan), shutdownChan), node.Online(), node.Repo(r), @@ -156,8 +159,12 @@ var runCmd = &cli.Command{ sigChan := make(chan os.Signal, 2) go func() { - <-sigChan - log.Warn("Shutting down..") + select { + case <-sigChan: + case <-shutdownChan: + } + + log.Warn("Shutting down...") if err := stop(context.TODO()); err != nil { log.Errorf("graceful shutting down failed: %s", err) } diff --git a/cmd/lotus-storage-miner/stop.go b/cmd/lotus-storage-miner/stop.go new file mode 100644 index 000000000..c52a51b95 --- /dev/null +++ b/cmd/lotus-storage-miner/stop.go @@ -0,0 +1,29 @@ +package main + +import ( + _ "net/http/pprof" + + "gopkg.in/urfave/cli.v2" + + lcli "github.com/filecoin-project/lotus/cli" +) + +var stopCmd = &cli.Command{ + Name: "stop", + Usage: "Stop a running lotus storage miner", + Flags: []cli.Flag{}, + Action: func(cctx *cli.Context) error { + api, closer, err := lcli.GetAPI(cctx) + if err != nil { + return err + } + defer closer() + + err = api.Shutdown(lcli.ReqContext(cctx)) + if err != nil { + return err + } + + return nil + }, +} diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 91af88a4e..6783ef0b1 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -29,6 +29,7 @@ import ( "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/vm" + lcli "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/lib/peermgr" "github.com/filecoin-project/lotus/metrics" "github.com/filecoin-project/lotus/node" @@ -44,6 +45,26 @@ const ( preTemplateFlag = "genesis-template" ) +var daemonStopCmd = &cli.Command{ + Name: "stop", + Usage: "Stop a running lotus daemon", + Flags: []cli.Flag{}, + Action: func(cctx *cli.Context) error { + api, closer, err := lcli.GetAPI(cctx) + if err != nil { + return err + } + defer closer() + + err = api.Shutdown(lcli.ReqContext(cctx)) + if err != nil { + return err + } + + return nil + }, +} + // DaemonCmd is the `go-lotus daemon` command var DaemonCmd = &cli.Command{ Name: "daemon", @@ -170,12 +191,15 @@ var DaemonCmd = &cli.Command{ genesis = node.Override(new(modules.Genesis), testing.MakeGenesis(cctx.String(makeGenFlag), cctx.String(preTemplateFlag))) } + shutdownChan := make(chan struct{}) + var api api.FullNode stop, err := node.New(ctx, node.FullAPI(&api), node.Override(new(dtypes.Bootstrapper), isBootstrapper), + node.Override(new(dtypes.ShutdownChan), shutdownChan), node.Online(), node.Repo(r), @@ -221,7 +245,10 @@ var DaemonCmd = &cli.Command{ } // TODO: properly parse api endpoint (or make it a URL) - return serveRPC(api, stop, endpoint) + return serveRPC(api, stop, endpoint, shutdownChan) + }, + Subcommands: []*cli.Command{ + daemonStopCmd, }, } diff --git a/cmd/lotus/rpc.go b/cmd/lotus/rpc.go index 7b1fc7286..a1cf7c80d 100644 --- a/cmd/lotus/rpc.go +++ b/cmd/lotus/rpc.go @@ -28,7 +28,7 @@ import ( var log = logging.Logger("main") -func serveRPC(a api.FullNode, stop node.StopFunc, addr multiaddr.Multiaddr) error { +func serveRPC(a api.FullNode, stop node.StopFunc, addr multiaddr.Multiaddr, shutdownCh <-chan struct{}) error { rpcServer := jsonrpc.NewServer() rpcServer.Register("Filecoin", apistruct.PermissionedFullAPI(a)) @@ -62,17 +62,23 @@ func serveRPC(a api.FullNode, stop node.StopFunc, addr multiaddr.Multiaddr) erro srv := &http.Server{Handler: http.DefaultServeMux} - sigChan := make(chan os.Signal, 2) + sigCh := make(chan os.Signal, 2) go func() { - <-sigChan + select { + case <-sigCh: + case <-shutdownCh: + } + + log.Warn("Shutting down...") if err := srv.Shutdown(context.TODO()); err != nil { log.Errorf("shutting down RPC server failed: %s", err) } if err := stop(context.TODO()); err != nil { log.Errorf("graceful shutting down failed: %s", err) } + log.Warn("Graceful shutdown successful") }() - signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) + signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT) return srv.Serve(manet.NetListener(lst)) } diff --git a/node/builder.go b/node/builder.go index d054be7a5..c654ebc86 100644 --- a/node/builder.go +++ b/node/builder.go @@ -150,6 +150,7 @@ func defaults() []Option { Override(new(helpers.MetricsCtx), context.Background), Override(new(record.Validator), modules.RecordValidator), Override(new(dtypes.Bootstrapper), dtypes.Bootstrapper(false)), + Override(new(dtypes.ShutdownChan), make(chan struct{})), // Filecoin modules diff --git a/node/impl/common/common.go b/node/impl/common/common.go index 5042d2ede..73f2de9e6 100644 --- a/node/impl/common/common.go +++ b/node/impl/common/common.go @@ -25,9 +25,10 @@ import ( type CommonAPI struct { fx.In - APISecret *dtypes.APIAlg - Host host.Host - Router lp2p.BaseIpfsRouting + APISecret *dtypes.APIAlg + Host host.Host + Router lp2p.BaseIpfsRouting + ShutdownChan dtypes.ShutdownChan } type jwtPayload struct { @@ -115,4 +116,9 @@ func (a *CommonAPI) LogSetLevel(ctx context.Context, subsystem, level string) er return logging.SetLogLevel(subsystem, level) } +func (a *CommonAPI) Shutdown(ctx context.Context) error { + a.ShutdownChan <- struct{}{} + return nil +} + var _ api.Common = &CommonAPI{} diff --git a/node/modules/dtypes/shutdown.go b/node/modules/dtypes/shutdown.go new file mode 100644 index 000000000..6497b0938 --- /dev/null +++ b/node/modules/dtypes/shutdown.go @@ -0,0 +1,5 @@ +package dtypes + +// ShutdownChan is a channel to which you send a value if you intend to shut +// down the daemon (or storage miner), including the node and RPC server. +type ShutdownChan chan struct{}