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) {