Skip to content

Commit d786383

Browse files
committed
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 ipfs#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.
1 parent c03b51d commit d786383

File tree

2 files changed

+30
-17
lines changed

2 files changed

+30
-17
lines changed

cmd/ipfs/daemon.go

+22-2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ func daemonFunc(req cmds.Request, res cmds.Response) {
8181
// let the user know we're going.
8282
fmt.Printf("Initializing daemon...\n")
8383

84+
ctx := req.Context()
85+
86+
go func() {
87+
select {
88+
case <-ctx.Context.Done():
89+
fmt.Println("Received interrupt signal, shutting down...")
90+
}
91+
}()
92+
8493
// first, whether user has provided the initialization flag. we may be
8594
// running in an uninitialized state.
8695
initialize, _, err := req.Option(initOptionKwd).Bool()
@@ -109,7 +118,6 @@ func daemonFunc(req cmds.Request, res cmds.Response) {
109118
// message.
110119
// NB: It's safe to read the config without the daemon lock, but not safe
111120
// to write.
112-
ctx := req.Context()
113121
cfg, err := ctx.GetConfig()
114122
if err != nil {
115123
res.SetError(err, cmds.ErrNormal)
@@ -155,7 +163,19 @@ func daemonFunc(req cmds.Request, res cmds.Response) {
155163
res.SetError(err, cmds.ErrNormal)
156164
return
157165
}
158-
defer node.Close()
166+
167+
defer func() {
168+
// We wait for the node to close first, as the node has children
169+
// that it will wait for before closing, such as the API server.
170+
node.Close()
171+
172+
select {
173+
case <-ctx.Context.Done():
174+
log.Info("Gracefully shut down daemon")
175+
default:
176+
}
177+
}()
178+
159179
req.Context().ConstructNode = func() (*core.IpfsNode, error) {
160180
return node, nil
161181
}

cmd/ipfs/main.go

+8-15
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,9 @@ func main() {
142142
}
143143

144144
// ok, finally, run the command invocation.
145-
intrh := invoc.SetupInterruptHandler()
145+
intrh, ctx := invoc.SetupInterruptHandler(ctx)
146146
defer intrh.Close()
147+
147148
output, err := invoc.Run(ctx)
148149
if err != nil {
149150
printErr(err)
@@ -507,14 +508,15 @@ func (ih *IntrHandler) Handle(handler func(count int, ih *IntrHandler), sigs ...
507508
}()
508509
}
509510

510-
func (i *cmdInvocation) SetupInterruptHandler() io.Closer {
511+
func (i *cmdInvocation) SetupInterruptHandler(ctx context.Context) (io.Closer, context.Context) {
511512

512513
intrh := NewIntrHandler()
514+
ctx, cancelFunc := context.WithCancel(ctx)
515+
513516
handlerFunc := func(count int, ih *IntrHandler) {
514517
switch count {
515518
case 1:
516-
// first time, try to shut down
517-
fmt.Println("Received interrupt signal, shutting down...")
519+
fmt.Println() // Prevent un-terminated ^C character in terminal
518520

519521
ctx := i.req.Context()
520522

@@ -528,16 +530,7 @@ func (i *cmdInvocation) SetupInterruptHandler() io.Closer {
528530
ih.wg.Add(1)
529531
go func() {
530532
defer ih.wg.Done()
531-
532-
// TODO cancel the command context instead
533-
n, err := ctx.GetNode()
534-
if err != nil {
535-
log.Error(err)
536-
os.Exit(-1)
537-
}
538-
539-
n.Close()
540-
log.Info("Gracefully shut down.")
533+
cancelFunc()
541534
}()
542535

543536
default:
@@ -548,7 +541,7 @@ func (i *cmdInvocation) SetupInterruptHandler() io.Closer {
548541

549542
intrh.Handle(handlerFunc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
550543

551-
return intrh
544+
return intrh, ctx
552545
}
553546

554547
func profileIfEnabled() (func(), error) {

0 commit comments

Comments
 (0)