Skip to content

Commit

Permalink
Merge pull request #30340 from ijrandom/master
Browse files Browse the repository at this point in the history
Fix #30311: dockerd leaks ExecIds on failed exec -i
  • Loading branch information
AkihiroSuda committed Feb 14, 2017
2 parents 4163b5c + 3cc0d6b commit eac68db
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 22 deletions.
1 change: 1 addition & 0 deletions api/server/httputils/errors.go
Expand Up @@ -54,6 +54,7 @@ func GetHTTPErrorStatusCode(err error) int {
code int
}{
{"not found", http.StatusNotFound},
{"cannot find", http.StatusNotFound},
{"no such", http.StatusNotFound},
{"bad parameter", http.StatusBadRequest},
{"no command", http.StatusBadRequest},
Expand Down
17 changes: 12 additions & 5 deletions daemon/exec.go
Expand Up @@ -165,18 +165,25 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R
return fmt.Errorf("Error: Exec command %s is already running", ec.ID)
}
ec.Running = true
ec.Unlock()

c := d.containers.Get(ec.ContainerID)
logrus.Debugf("starting exec command %s in container %s", ec.ID, c.ID)
d.LogContainerEvent(c, "exec_start: "+ec.Entrypoint+" "+strings.Join(ec.Args, " "))

defer func() {
if err != nil {
ec.Lock()
ec.Running = false
exitCode := 126
ec.ExitCode = &exitCode
if err := ec.CloseStreams(); err != nil {
logrus.Errorf("failed to cleanup exec %s streams: %s", c.ID, err)
}
ec.Unlock()
c.ExecCommands.Delete(ec.ID)
}
}()
ec.Unlock()

c := d.containers.Get(ec.ContainerID)
logrus.Debugf("starting exec command %s in container %s", ec.ID, c.ID)
d.LogContainerEvent(c, "exec_start: "+ec.Entrypoint+" "+strings.Join(ec.Args, " "))

if ec.OpenStdin && stdin != nil {
r, w := io.Pipe()
Expand Down
2 changes: 1 addition & 1 deletion daemon/monitor.go
Expand Up @@ -90,7 +90,7 @@ func (daemon *Daemon) StateChanged(id string, e libcontainerd.StateInfo) error {
execConfig.Running = false
execConfig.StreamConfig.Wait()
if err := execConfig.CloseStreams(); err != nil {
logrus.Errorf("%s: %s", c.ID, err)
logrus.Errorf("failed to cleanup exec %s streams: %s", c.ID, err)
}

// remove the exec command from the container's store only and not the
Expand Down
79 changes: 63 additions & 16 deletions integration-cli/docker_api_exec_test.go
Expand Up @@ -120,21 +120,7 @@ func (s *DockerSuite) TestExecAPIStartMultipleTimesError(c *check.C) {
runSleepingContainer(c, "-d", "--name", "test")
execID := createExec(c, "test")
startExec(c, execID, http.StatusOK)

timeout := time.After(60 * time.Second)
var execJSON struct{ Running bool }
for {
select {
case <-timeout:
c.Fatal("timeout waiting for exec to start")
default:
}

inspectExec(c, execID, &execJSON)
if !execJSON.Running {
break
}
}
waitForExec(c, execID)

startExec(c, execID, http.StatusConflict)
}
Expand Down Expand Up @@ -169,8 +155,43 @@ func (s *DockerSuite) TestExecAPIStartWithDetach(c *check.C) {
}
}

// #30311
func (s *DockerSuite) TestExecAPIStartValidCommand(c *check.C) {
name := "exec_test"
dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")

id := createExecCmd(c, name, "true")
startExec(c, id, http.StatusOK)

waitForExec(c, id)

var inspectJSON struct{ ExecIDs []string }
inspectContainer(c, name, &inspectJSON)

c.Assert(inspectJSON.ExecIDs, checker.IsNil)
}

// #30311
func (s *DockerSuite) TestExecAPIStartInvalidCommand(c *check.C) {
name := "exec_test"
dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")

id := createExecCmd(c, name, "invalid")
startExec(c, id, http.StatusNotFound)
waitForExec(c, id)

var inspectJSON struct{ ExecIDs []string }
inspectContainer(c, name, &inspectJSON)

c.Assert(inspectJSON.ExecIDs, checker.IsNil)
}

func createExec(c *check.C, name string) string {
_, b, err := request.SockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": []string{"true"}}, daemonHost())
return createExecCmd(c, name, "true")
}

func createExecCmd(c *check.C, name string, cmd string) string {
_, b, err := request.SockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": []string{cmd}}, daemonHost())
c.Assert(err, checker.IsNil, check.Commentf(string(b)))

createResp := struct {
Expand Down Expand Up @@ -198,3 +219,29 @@ func inspectExec(c *check.C, id string, out interface{}) {
err = json.NewDecoder(body).Decode(out)
c.Assert(err, checker.IsNil)
}

func waitForExec(c *check.C, id string) {
timeout := time.After(60 * time.Second)
var execJSON struct{ Running bool }
for {
select {
case <-timeout:
c.Fatal("timeout waiting for exec to start")
default:
}

inspectExec(c, id, &execJSON)
if !execJSON.Running {
break
}
}
}

func inspectContainer(c *check.C, id string, out interface{}) {
resp, body, err := request.SockRequestRaw("GET", fmt.Sprintf("/containers/%s/json", id), nil, "", daemonHost())
c.Assert(err, checker.IsNil)
defer body.Close()
c.Assert(resp.StatusCode, checker.Equals, http.StatusOK)
err = json.NewDecoder(body).Decode(out)
c.Assert(err, checker.IsNil)
}

0 comments on commit eac68db

Please sign in to comment.