From b8464c1c9baf6714242cbd9b834344eff2ebfba8 Mon Sep 17 00:00:00 2001 From: Zhang Wei Date: Fri, 19 Aug 2016 15:14:16 +0800 Subject: [PATCH] Using waitExitOrRemoved for `docker start` Currently start command will invoke getExitCode - which is based on Inspect API - to get returned exit code after container exits. There's two race conditions here: if container is started with Restart Policy, there's chance that the container is restarted quickly before it calls getExitCode, under such circumstance, the exit code is wrong. if container is started with --rm, it's possible that container is removed before getExitCode, in this situation, you can't get correct exit code either. Replace getExitCode with waitExitOrRemoved can solve this problem. Signed-off-by: Zhang Wei --- api/client/container/start.go | 25 ++++++++++++++++-------- integration-cli/docker_cli_start_test.go | 12 ++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/api/client/container/start.go b/api/client/container/start.go index 1520b046216d9..9b291ad2c4a02 100644 --- a/api/client/container/start.go +++ b/api/client/container/start.go @@ -90,8 +90,8 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error { resp, errAttach := dockerCli.Client().ContainerAttach(ctx, c.ID, options) if errAttach != nil && errAttach != httputil.ErrPersistEOF { // ContainerAttach return an ErrPersistEOF (connection closed) - // means server met an error and put it in Hijacked connection - // keep the error and read detailed error message from hijacked connection + // means server met an error and already put it in Hijacked connection, + // we would keep the error and read the detailed error message from hijacked connection return errAttach } defer resp.Close() @@ -103,14 +103,22 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error { return errHijack }) - // 3. Start the container. + // 3. We should open a channel for receiving status code of the container + // no matter it's detached, removed on daemon side(--rm) or exit normally. + statusChan, statusErr := waitExitOrRemoved(dockerCli, context.Background(), c.ID, c.HostConfig.AutoRemove) + + // 4. Start the container. if err := dockerCli.Client().ContainerStart(ctx, c.ID, types.ContainerStartOptions{}); err != nil { cancelFun() <-cErr + if c.HostConfig.AutoRemove && statusErr == nil { + // wait container to be removed + <-statusChan + } return err } - // 4. Wait for attachment to break. + // 5. Wait for attachment to break. if c.Config.Tty && dockerCli.IsTerminalOut() { if err := dockerCli.MonitorTtySize(ctx, c.ID, false); err != nil { fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err) @@ -119,11 +127,12 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error { if attchErr := <-cErr; attchErr != nil { return attchErr } - _, status, err := getExitCode(dockerCli, ctx, c.ID) - if err != nil { - return err + + if statusErr != nil { + return fmt.Errorf("can't get container's exit code: %v", statusErr) } - if status != 0 { + + if status := <-statusChan; status != 0 { return cli.StatusError{StatusCode: status} } } else { diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go index 1c6a3cf2ae038..f96528f7d623a 100644 --- a/integration-cli/docker_cli_start_test.go +++ b/integration-cli/docker_cli_start_test.go @@ -185,3 +185,15 @@ func (s *DockerSuite) TestStartAttachWithRename(c *check.C) { _, stderr, _, _ := runCommandWithStdoutStderr(exec.Command(dockerBinary, "start", "-a", "before")) c.Assert(stderr, checker.Not(checker.Contains), "No such container") } + +func (s *DockerSuite) TestStartReturnCorrectExitCode(c *check.C) { + dockerCmd(c, "create", "--restart=on-failure:2", "--name", "withRestart", "busybox", "sh", "-c", "exit 11") + dockerCmd(c, "create", "--rm", "--name", "withRm", "busybox", "sh", "-c", "exit 12") + + _, exitCode, err := dockerCmdWithError("start", "-a", "withRestart") + c.Assert(err, checker.NotNil) + c.Assert(exitCode, checker.Equals, 11) + _, exitCode, err = dockerCmdWithError("start", "-a", "withRm") + c.Assert(err, checker.NotNil) + c.Assert(exitCode, checker.Equals, 12) +}