From cf6a268cd3b9b000f7554d679df8bb3f7e6cf31d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Fri, 17 Apr 2015 18:23:04 +0200 Subject: [PATCH] Handle ipfs command interruption by cancelling the command context Instead of assuming the command is the daemon command and closing the node, which resulted in bugs like #1053, we cancel the context and let the context children detect the cancellation and gracefully clean up after themselves. The shutdown logging has been moved into the daemon command, where it makes more sense, so that commands like ping will not print out the same output on cancellation. --- cmd/ipfs/daemon.go | 24 ++++++++++++++++++++++-- cmd/ipfs/main.go | 23 ++++++++--------------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/cmd/ipfs/daemon.go b/cmd/ipfs/daemon.go index b2104ce6f..56d0dc811 100644 --- a/cmd/ipfs/daemon.go +++ b/cmd/ipfs/daemon.go @@ -80,6 +80,15 @@ func daemonFunc(req cmds.Request, res cmds.Response) { // let the user know we're going. fmt.Printf("Initializing daemon...\n") + ctx := req.Context() + + go func() { + select { + case <-ctx.Context.Done(): + fmt.Println("Received interrupt signal, shutting down...") + } + }() + // first, whether user has provided the initialization flag. we may be // running in an uninitialized state. initialize, _, err := req.Option(initOptionKwd).Bool() @@ -111,7 +120,6 @@ func daemonFunc(req cmds.Request, res cmds.Response) { return } - ctx := req.Context() cfg, err := ctx.GetConfig() if err != nil { res.SetError(err, cmds.ErrNormal) @@ -149,7 +157,19 @@ func daemonFunc(req cmds.Request, res cmds.Response) { res.SetError(err, cmds.ErrNormal) return } - defer node.Close() + + defer func() { + // We wait for the node to close first, as the node has children + // that it will wait for before closing, such as the API server. + node.Close() + + select { + case <-ctx.Context.Done(): + log.Info("Gracefully shut down daemon") + default: + } + }() + req.Context().ConstructNode = func() (*core.IpfsNode, error) { return node, nil } diff --git a/cmd/ipfs/main.go b/cmd/ipfs/main.go index 21ec9b937..4b69e77f8 100644 --- a/cmd/ipfs/main.go +++ b/cmd/ipfs/main.go @@ -141,8 +141,9 @@ func main() { } // ok, finally, run the command invocation. - intrh := invoc.SetupInterruptHandler() + intrh, ctx := invoc.SetupInterruptHandler(ctx) defer intrh.Close() + output, err := invoc.Run(ctx) if err != nil { printErr(err) @@ -514,14 +515,15 @@ func (ih *IntrHandler) Handle(handler func(count int, ih *IntrHandler), sigs ... }() } -func (i *cmdInvocation) SetupInterruptHandler() io.Closer { +func (i *cmdInvocation) SetupInterruptHandler(ctx context.Context) (io.Closer, context.Context) { intrh := NewIntrHandler() + ctx, cancelFunc := context.WithCancel(ctx) + handlerFunc := func(count int, ih *IntrHandler) { switch count { case 1: - // first time, try to shut down - fmt.Println("Received interrupt signal, shutting down...") + fmt.Println() // Prevent un-terminated ^C character in terminal ctx := i.req.Context() @@ -535,16 +537,7 @@ func (i *cmdInvocation) SetupInterruptHandler() io.Closer { ih.wg.Add(1) go func() { defer ih.wg.Done() - - // TODO cancel the command context instead - n, err := ctx.GetNode() - if err != nil { - log.Error(err) - os.Exit(-1) - } - - n.Close() - log.Info("Gracefully shut down.") + cancelFunc() }() default: @@ -555,7 +548,7 @@ func (i *cmdInvocation) SetupInterruptHandler() io.Closer { intrh.Handle(handlerFunc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) - return intrh + return intrh, ctx } func profileIfEnabled() (func(), error) {