Skip to content

Commit 05f3cfb

Browse files
ijrandomruncom
authored andcommitted
BACKPORT: Fix #303111: dockerd leaks ExecIds on failed exec -i
Ref: #257 Signed-off-by: Dmitry Shyshkin <dmitry@shyshkin.org.ua> Signed-off-by: Antonio Murdaca <runcom@redhat.com>
1 parent b16f9cd commit 05f3cfb

4 files changed

Lines changed: 78 additions & 22 deletions

File tree

api/server/httputils/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func GetHTTPErrorStatusCode(err error) int {
5454
code int
5555
}{
5656
{"not found", http.StatusNotFound},
57+
{"cannot find", http.StatusNotFound},
5758
{"no such", http.StatusNotFound},
5859
{"bad parameter", http.StatusBadRequest},
5960
{"no command", http.StatusBadRequest},

daemon/exec.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -159,18 +159,25 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R
159159
return fmt.Errorf("Error: Exec command %s is already running", ec.ID)
160160
}
161161
ec.Running = true
162+
ec.Unlock()
163+
164+
c := d.containers.Get(ec.ContainerID)
165+
logrus.Debugf("starting exec command %s in container %s", ec.ID, c.ID)
166+
d.LogContainerEvent(c, "exec_start: "+ec.Entrypoint+" "+strings.Join(ec.Args, " "))
167+
162168
defer func() {
163169
if err != nil {
170+
ec.Lock()
164171
ec.Running = false
165172
exitCode := 126
166173
ec.ExitCode = &exitCode
174+
if err := ec.CloseStreams(); err != nil {
175+
logrus.Errorf("failed to cleanup exec %s streams: %s", c.ID, err)
176+
}
177+
ec.Unlock()
178+
c.ExecCommands.Delete(ec.ID)
167179
}
168180
}()
169-
ec.Unlock()
170-
171-
c := d.containers.Get(ec.ContainerID)
172-
logrus.Debugf("starting exec command %s in container %s", ec.ID, c.ID)
173-
d.LogContainerEvent(c, "exec_start: "+ec.Entrypoint+" "+strings.Join(ec.Args, " "))
174181

175182
if ec.OpenStdin && stdin != nil {
176183
r, w := io.Pipe()

daemon/monitor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func (daemon *Daemon) StateChanged(id string, e libcontainerd.StateInfo) error {
6565
execConfig.Running = false
6666
execConfig.StreamConfig.Wait()
6767
if err := execConfig.CloseStreams(); err != nil {
68-
logrus.Errorf("%s: %s", c.ID, err)
68+
logrus.Errorf("failed to cleanup exec %s streams: %s", c.ID, err)
6969
}
7070

7171
// remove the exec command from the container's store only and not the

integration-cli/docker_api_exec_test.go

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111
"time"
1212

13+
"github.com/docker/docker/integration-cli/request"
1314
"github.com/docker/docker/pkg/integration/checker"
1415
"github.com/go-check/check"
1516
)
@@ -103,21 +104,7 @@ func (s *DockerSuite) TestExecApiStartMultipleTimesError(c *check.C) {
103104
runSleepingContainer(c, "-d", "--name", "test")
104105
execID := createExec(c, "test")
105106
startExec(c, execID, http.StatusOK)
106-
107-
timeout := time.After(60 * time.Second)
108-
var execJSON struct{ Running bool }
109-
for {
110-
select {
111-
case <-timeout:
112-
c.Fatal("timeout waiting for exec to start")
113-
default:
114-
}
115-
116-
inspectExec(c, execID, &execJSON)
117-
if !execJSON.Running {
118-
break
119-
}
120-
}
107+
waitForExec(c, execID)
121108

122109
startExec(c, execID, http.StatusConflict)
123110
}
@@ -152,8 +139,43 @@ func (s *DockerSuite) TestExecApiStartWithDetach(c *check.C) {
152139
}
153140
}
154141

142+
// #30311
143+
func (s *DockerSuite) TestExecAPIStartValidCommand(c *check.C) {
144+
name := "exec_test"
145+
dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
146+
147+
id := createExecCmd(c, name, "true")
148+
startExec(c, id, http.StatusOK)
149+
150+
waitForExec(c, id)
151+
152+
var inspectJSON struct{ ExecIDs []string }
153+
inspectContainer(c, name, &inspectJSON)
154+
155+
c.Assert(inspectJSON.ExecIDs, checker.IsNil)
156+
}
157+
158+
// #30311
159+
func (s *DockerSuite) TestExecAPIStartInvalidCommand(c *check.C) {
160+
name := "exec_test"
161+
dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
162+
163+
id := createExecCmd(c, name, "invalid")
164+
startExec(c, id, http.StatusNotFound)
165+
waitForExec(c, id)
166+
167+
var inspectJSON struct{ ExecIDs []string }
168+
inspectContainer(c, name, &inspectJSON)
169+
170+
c.Assert(inspectJSON.ExecIDs, checker.IsNil)
171+
}
172+
155173
func createExec(c *check.C, name string) string {
156-
_, b, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": []string{"true"}})
174+
return createExecCmd(c, name, "true")
175+
}
176+
177+
func createExecCmd(c *check.C, name string, cmd string) string {
178+
_, b, err := request.SockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": []string{cmd}}, daemonHost())
157179
c.Assert(err, checker.IsNil, check.Commentf(string(b)))
158180

159181
createResp := struct {
@@ -181,3 +203,29 @@ func inspectExec(c *check.C, id string, out interface{}) {
181203
err = json.NewDecoder(body).Decode(out)
182204
c.Assert(err, checker.IsNil)
183205
}
206+
207+
func waitForExec(c *check.C, id string) {
208+
timeout := time.After(60 * time.Second)
209+
var execJSON struct{ Running bool }
210+
for {
211+
select {
212+
case <-timeout:
213+
c.Fatal("timeout waiting for exec to start")
214+
default:
215+
}
216+
217+
inspectExec(c, id, &execJSON)
218+
if !execJSON.Running {
219+
break
220+
}
221+
}
222+
}
223+
224+
func inspectContainer(c *check.C, id string, out interface{}) {
225+
resp, body, err := request.SockRequestRaw("GET", fmt.Sprintf("/containers/%s/json", id), nil, "", daemonHost())
226+
c.Assert(err, checker.IsNil)
227+
defer body.Close()
228+
c.Assert(resp.StatusCode, checker.Equals, http.StatusOK)
229+
err = json.NewDecoder(body).Decode(out)
230+
c.Assert(err, checker.IsNil)
231+
}

0 commit comments

Comments
 (0)