Skip to content

Commit

Permalink
Handle ipfs command interruption by cancelling the command context
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
torarnv committed Apr 17, 2015
1 parent c03b51d commit d786383
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 17 deletions.
24 changes: 22 additions & 2 deletions cmd/ipfs/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,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()
Expand Down Expand Up @@ -109,7 +118,6 @@ func daemonFunc(req cmds.Request, res cmds.Response) {
// message.
// NB: It's safe to read the config without the daemon lock, but not safe
// to write.
ctx := req.Context()
cfg, err := ctx.GetConfig()
if err != nil {
res.SetError(err, cmds.ErrNormal)
Expand Down Expand Up @@ -155,7 +163,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
}
Expand Down
23 changes: 8 additions & 15 deletions cmd/ipfs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,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)
Expand Down Expand Up @@ -507,14 +508,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()

Expand All @@ -528,16 +530,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:
Expand All @@ -548,7 +541,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) {
Expand Down

0 comments on commit d786383

Please sign in to comment.