diff --git a/commands.go b/commands.go index df33dfc30..556debdbb 100644 --- a/commands.go +++ b/commands.go @@ -6,6 +6,7 @@ import ( "bytes" "encoding/base64" "encoding/json" + "errors" "flag" "fmt" "github.com/dotcloud/docker/auth" @@ -36,6 +37,10 @@ var ( VERSION string ) +var ( + ErrConnectionRefused = errors.New("Can't connect to docker daemon. Is 'docker -d' running on this host?") +) + func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) { methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:]) return reflect.TypeOf(cli).MethodByName(methodName) @@ -1258,7 +1263,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { if container.Config.Tty { if err := cli.monitorTtySize(cmd.Arg(0)); err != nil { - return err + utils.Debugf("Error monitoring tty size: %s", err) } } @@ -1567,12 +1572,12 @@ func (cli *DockerCli) CmdRun(args ...string) error { // Detached mode <-wait } else { - status, err := waitForExit(cli, runResult.ID) + status, err := getExitCode(cli, runResult.ID) if err != nil { return err } if status != 0 { - return &utils.StatusError{status} + return &utils.StatusError{Status: status} } } @@ -1638,7 +1643,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, dial, err := net.Dial(cli.proto, cli.addr) if err != nil { if strings.Contains(err.Error(), "connection refused") { - return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") + return nil, -1, ErrConnectionRefused } return nil, -1, err } @@ -1647,7 +1652,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, defer clientconn.Close() if err != nil { if strings.Contains(err.Error(), "connection refused") { - return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") + return nil, -1, ErrConnectionRefused } return nil, -1, err } @@ -1866,7 +1871,11 @@ func (cli *DockerCli) LoadConfigFile() (err error) { func waitForExit(cli *DockerCli, containerId string) (int, error) { body, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil) if err != nil { - return -1, err + // If we can't connect, then the daemon probably died. + if err != ErrConnectionRefused { + return -1, err + } + return -1, nil } var out APIWait @@ -1876,6 +1885,22 @@ func waitForExit(cli *DockerCli, containerId string) (int, error) { return out.StatusCode, nil } +func getExitCode(cli *DockerCli, containerId string) (int, error) { + body, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil) + if err != nil { + // If we can't connect, then the daemon probably died. + if err != ErrConnectionRefused { + return -1, err + } + return -1, nil + } + c := &Container{} + if err := json.Unmarshal(body, c); err != nil { + return -1, err + } + return c.State.ExitCode, nil +} + func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli { var ( isTerminal = false diff --git a/commands_test.go b/commands_test.go index 2946da879..dbd85be0c 100644 --- a/commands_test.go +++ b/commands_test.go @@ -369,6 +369,110 @@ func TestRunAttachStdin(t *testing.T) { } } +// TestRunDetach checks attaching and detaching with the escape sequence. +func TestRunDetach(t *testing.T) { + + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + + cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalRuntime) + + ch := make(chan struct{}) + go func() { + defer close(ch) + cli.CmdRun("-i", "-t", unitTestImageID, "cat") + }() + + setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { + if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { + t.Fatal(err) + } + }) + + container := globalRuntime.List()[0] + + setTimeout(t, "Escape sequence timeout", 5*time.Second, func() { + stdinPipe.Write([]byte{'', ''}) + if err := stdinPipe.Close(); err != nil { + t.Fatal(err) + } + }) + + // wait for CmdRun to return + setTimeout(t, "Waiting for CmdRun timed out", 5*time.Second, func() { + <-ch + }) + + time.Sleep(500 * time.Millisecond) + if !container.State.Running { + t.Fatal("The detached container should be still running") + } + + setTimeout(t, "Waiting for container to die timed out", 20*time.Second, func() { + container.Kill() + container.Wait() + }) +} + +// TestAttachDetach checks that attach in tty mode can be detached +func TestAttachDetach(t *testing.T) { + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + + cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalRuntime) + + go stdout.Read(make([]byte, 1024)) + setTimeout(t, "Starting container timed out", 2*time.Second, func() { + if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil { + t.Fatal(err) + } + }) + + container := globalRuntime.List()[0] + + stdin, stdinPipe = io.Pipe() + stdout, stdoutPipe = io.Pipe() + cli = NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + + ch := make(chan struct{}) + go func() { + defer close(ch) + if err := cli.CmdAttach(container.ShortID()); err != nil { + t.Fatal(err) + } + }() + + setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { + if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { + t.Fatal(err) + } + }) + + setTimeout(t, "Escape sequence timeout", 5*time.Second, func() { + stdinPipe.Write([]byte{'', ''}) + if err := stdinPipe.Close(); err != nil { + t.Fatal(err) + } + }) + + // wait for CmdRun to return + setTimeout(t, "Waiting for CmdAttach timed out", 5*time.Second, func() { + <-ch + }) + + time.Sleep(500 * time.Millisecond) + if !container.State.Running { + t.Fatal("The detached container should be still running") + } + + setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() { + container.Kill() + container.Wait() + }) +} + // Expected behaviour, the process stays alive when the client disconnects func TestAttachDisconnect(t *testing.T) { stdin, stdinPipe := io.Pipe() diff --git a/container.go b/container.go index 62709a9f3..6bca3dfbf 100644 --- a/container.go +++ b/container.go @@ -964,14 +964,19 @@ func (container *Container) monitor() { } } utils.Debugf("Process finished") - if container.runtime != nil && container.runtime.srv != nil { - container.runtime.srv.LogEvent("die", container.ShortID(), container.runtime.repositories.ImageName(container.Image)) - } + exitCode := -1 if container.cmd != nil { exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() } + // Report status back + container.State.setStopped(exitCode) + + if container.runtime != nil && container.runtime.srv != nil { + container.runtime.srv.LogEvent("die", container.ShortID(), container.runtime.repositories.ImageName(container.Image)) + } + // Cleanup container.releaseNetwork() if container.Config.OpenStdin { @@ -1001,9 +1006,6 @@ func (container *Container) monitor() { container.stdin, container.stdinPipe = io.Pipe() } - // Report status back - container.State.setStopped(exitCode) - // Release the lock close(container.waitLock)