From 24dac22892cc3c8f34e90222f64b87d750b5025e Mon Sep 17 00:00:00 2001 From: Andrea Luzzardi Date: Mon, 21 Jan 2013 18:03:23 -0800 Subject: [PATCH] Docker: Clean shutdown of containers. Stop() will send a SIGTERM to the init process of the container and give it 10 seconds to shutdown properly. After the timeout is reached, the process will be force killed (SIGKILL). Also available is Kill() which kills the process on the spot. --- container.go | 71 +++++++++++++++++++++++++++++++++++++++++------ container_test.go | 10 +++---- 2 files changed, 67 insertions(+), 14 deletions(-) diff --git a/container.go b/container.go index 77fd7c0fec..d89f99e474 100644 --- a/container.go +++ b/container.go @@ -5,10 +5,12 @@ import ( "errors" "io" "io/ioutil" + "log" "os" "os/exec" "path" "syscall" + "time" ) type Container struct { @@ -176,22 +178,57 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) { func (container *Container) monitor() { container.cmd.Wait() + exitCode := container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() + + // Cleanup container container.stdout.Close() container.stderr.Close() - container.State.setStopped(container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()) + if err := container.Filesystem.Umount(); err != nil { + log.Printf("%v: Failed to umount filesystem: %v", container.Name, err) + } + + // Report status back + container.State.setStopped(exitCode) +} + +func (container *Container) kill() error { + // This will cause the main container process to receive a SIGKILL + if err := exec.Command("/usr/bin/lxc-stop", "-n", container.Name).Run(); err != nil { + return err + } + + // Wait for the container to be actually stopped + if err := exec.Command("/usr/bin/lxc-wait", "-n", container.Name, "-s", "STOPPED").Run(); err != nil { + return err + } + return nil +} + +func (container *Container) Kill() error { + if !container.State.Running { + return nil + } + return container.kill() } func (container *Container) Stop() error { - if container.State.Running { - if err := exec.Command("/usr/bin/lxc-stop", "-n", container.Name).Run(); err != nil { - return err - } - //FIXME: We should lxc-wait for the container to stop + if !container.State.Running { + return nil } - if err := container.Filesystem.Umount(); err != nil { - // FIXME: Do not abort, probably already umounted? - return nil + // 1. Send a SIGTERM + if err := exec.Command("/usr/bin/lxc-kill", "-n", container.Name, "15").Run(); err != nil { + return err + } + + // 2. Wait for the process to exit on its own + if err := container.WaitTimeout(10 * time.Second); err != nil { + log.Printf("Container %v failed to exit within 10 seconds of SIGTERM", container.Name) + } + + // 3. Force kill + if err := container.kill(); err != nil { + return err } return nil } @@ -201,3 +238,19 @@ func (container *Container) Wait() { container.State.wait() } } + +func (container *Container) WaitTimeout(timeout time.Duration) error { + done := make(chan bool) + go func() { + container.Wait() + done <- true + }() + + select { + case <-time.After(timeout): + return errors.New("Timed Out") + case <-done: + return nil + } + return nil +} diff --git a/container_test.go b/container_test.go index 650d2a07f9..e348a2b4b0 100644 --- a/container_test.go +++ b/container_test.go @@ -98,15 +98,15 @@ func TestOutput(t *testing.T) { } } -func TestStop(t *testing.T) { +func TestKill(t *testing.T) { docker, err := newTestDocker() if err != nil { t.Fatal(err) } container, err := docker.Create( "stop_test", - "sleep", - []string{"300"}, + "cat", + []string{"/dev/zero"}, []string{"/var/lib/docker/images/ubuntu"}, &Config{}, ) @@ -124,7 +124,7 @@ func TestStop(t *testing.T) { if !container.State.Running { t.Errorf("Container should be running") } - if err := container.Stop(); err != nil { + if err := container.Kill(); err != nil { t.Fatal(err) } if container.State.Running { @@ -135,7 +135,7 @@ func TestStop(t *testing.T) { t.Errorf("Container shouldn't be running") } // Try stopping twice - if err := container.Stop(); err != nil { + if err := container.Kill(); err != nil { t.Fatal(err) } }