@cryptotaxi247 / kubo / commits / cf6a268cd

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.

Tor Arne Vestbø committed Apr 17, 2015 at 18:23 UTC cf6a268cd3b9b000f7554d679df8bb3f7e6cf31d
2 files changed +30 -17
cmd/ipfs/daemon.go
+22 -2
@@ -80,6 +80,15 @@ func daemonFunc(req cmds.Request, res cmds.Response) {
80 // let the user know we're going.
81 fmt.Printf("Initializing daemon...\n")
82
83 + ctx := req.Context()
84 +
85 + go func() {
86 + select {
87 + case <-ctx.Context.Done():
88 + fmt.Println("Received interrupt signal, shutting down...")
89 + }
90 + }()
91 +
92 // first, whether user has provided the initialization flag. we may be
93 // running in an uninitialized state.
94 initialize, _, err := req.Option(initOptionKwd).Bool()
@@ -111,7 +120,6 @@ func daemonFunc(req cmds.Request, res cmds.Response) {
120 return
121 }
122
114 - ctx := req.Context()
123 cfg, err := ctx.GetConfig()
124 if err != nil {
125 res.SetError(err, cmds.ErrNormal)
@@ -149,7 +157,19 @@ func daemonFunc(req cmds.Request, res cmds.Response) {
157 res.SetError(err, cmds.ErrNormal)
158 return
159 }
152 - defer node.Close()
160 +
161 + defer func() {
162 + // We wait for the node to close first, as the node has children
163 + // that it will wait for before closing, such as the API server.
164 + node.Close()
165 +
166 + select {
167 + case <-ctx.Context.Done():
168 + log.Info("Gracefully shut down daemon")
169 + default:
170 + }
171 + }()
172 +
173 req.Context().ConstructNode = func() (*core.IpfsNode, error) {
174 return node, nil
175 }
cmd/ipfs/main.go
+8 -15
@@ -141,8 +141,9 @@ func main() {
141 }
142
143 // ok, finally, run the command invocation.
144 - intrh := invoc.SetupInterruptHandler()
144 + intrh, ctx := invoc.SetupInterruptHandler(ctx)
145 defer intrh.Close()
146 +
147 output, err := invoc.Run(ctx)
148 if err != nil {
149 printErr(err)
@@ -514,14 +515,15 @@ func (ih *IntrHandler) Handle(handler func(count int, ih *IntrHandler), sigs ...
515 }()
516 }
517
517 -func (i *cmdInvocation) SetupInterruptHandler() io.Closer {
518 +func (i *cmdInvocation) SetupInterruptHandler(ctx context.Context) (io.Closer, context.Context) {
519
520 intrh := NewIntrHandler()
521 + ctx, cancelFunc := context.WithCancel(ctx)
522 +
523 handlerFunc := func(count int, ih *IntrHandler) {
524 switch count {
525 case 1:
523 - // first time, try to shut down
524 - fmt.Println("Received interrupt signal, shutting down...")
526 + fmt.Println() // Prevent un-terminated ^C character in terminal
527
528 ctx := i.req.Context()
529
@@ -535,16 +537,7 @@ func (i *cmdInvocation) SetupInterruptHandler() io.Closer {
537 ih.wg.Add(1)
538 go func() {
539 defer ih.wg.Done()
538 -
539 - // TODO cancel the command context instead
540 - n, err := ctx.GetNode()
541 - if err != nil {
542 - log.Error(err)
543 - os.Exit(-1)
544 - }
545 -
546 - n.Close()
547 - log.Info("Gracefully shut down.")
540 + cancelFunc()
541 }()
542
543 default:
@@ -555,7 +548,7 @@ func (i *cmdInvocation) SetupInterruptHandler() io.Closer {
548
549 intrh.Handle(handlerFunc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
550
558 - return intrh
551 + return intrh, ctx
552 }
553
554 func profileIfEnabled() (func(), error) {