Skip to content
This repository was archived by the owner on Feb 8, 2021. It is now read-only.

Commit 1a60a80

Browse files
committed
Fix panic on starting exec more than once
Issue was caused when exec is tarted, exits, then stated again. In this case, `Close` is called twice, which closes a channel twice. Changes execConfig.ExitCode to a pointer so we can test if the it has been set or not. This allows us to return early when the exec has already been run. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
1 parent 9b63019 commit 1a60a80

File tree

4 files changed

+65
-17
lines changed

4 files changed

+65
-17
lines changed

daemon/exec.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.
135135
}
136136

137137
ec.Lock()
138+
if ec.ExitCode != nil {
139+
ec.Unlock()
140+
return derr.ErrorCodeExecExited.WithArgs(ec.ID)
141+
}
142+
138143
if ec.Running {
139144
ec.Unlock()
140145
return derr.ErrorCodeExecRunning.WithArgs(ec.ID)
@@ -214,7 +219,7 @@ func (d *Daemon) Exec(c *container.Container, execConfig *exec.Config, pipes *ex
214219
exitStatus = 128
215220
}
216221

217-
execConfig.ExitCode = exitStatus
222+
execConfig.ExitCode = &exitStatus
218223
execConfig.Running = false
219224

220225
return exitStatus, err

daemon/exec/exec.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type Config struct {
1818
*runconfig.StreamConfig
1919
ID string
2020
Running bool
21-
ExitCode int
21+
ExitCode *int
2222
ProcessConfig *execdriver.ProcessConfig
2323
OpenStdin bool
2424
OpenStderr bool

errors/daemon.go

+9
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,15 @@ var (
742742
HTTPStatusCode: http.StatusInternalServerError,
743743
})
744744

745+
// ErrorCodeExecExited is generated when we try to start an exec
746+
// but its already running.
747+
ErrorCodeExecExited = errcode.Register(errGroup, errcode.ErrorDescriptor{
748+
Value: "EXECEXITED",
749+
Message: "Error: Exec command %s has already run",
750+
Description: "An attempt to start an 'exec' was made, but 'exec' was already run",
751+
HTTPStatusCode: http.StatusConflict,
752+
})
753+
745754
// ErrorCodeExecCantRun is generated when we try to start an exec
746755
// but it failed for some reason.
747756
ErrorCodeExecCantRun = errcode.Register(errGroup, errcode.ErrorDescriptor{

integration-cli/docker_api_exec_test.go

+49-15
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"net/http"
1010
"strings"
11+
"time"
1112

1213
"github.com/docker/docker/pkg/integration/checker"
1314
"github.com/go-check/check"
@@ -66,33 +67,23 @@ func (s *DockerSuite) TestExecAPIStart(c *check.C) {
6667
testRequires(c, DaemonIsLinux) // Uses pause/unpause but bits may be salvagable to Windows to Windows CI
6768
dockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
6869

69-
startExec := func(id string, code int) {
70-
resp, body, err := sockRequestRaw("POST", fmt.Sprintf("/exec/%s/start", id), strings.NewReader(`{"Detach": true}`), "application/json")
71-
c.Assert(err, checker.IsNil)
72-
73-
b, err := readBody(body)
74-
comment := check.Commentf("response body: %s", b)
75-
c.Assert(err, checker.IsNil, comment)
76-
c.Assert(resp.StatusCode, checker.Equals, code, comment)
77-
}
78-
7970
id := createExec(c, "test")
80-
startExec(id, http.StatusOK)
71+
startExec(c, id, http.StatusOK)
8172

8273
id = createExec(c, "test")
8374
dockerCmd(c, "stop", "test")
8475

85-
startExec(id, http.StatusNotFound)
76+
startExec(c, id, http.StatusNotFound)
8677

8778
dockerCmd(c, "start", "test")
88-
startExec(id, http.StatusNotFound)
79+
startExec(c, id, http.StatusNotFound)
8980

9081
// make sure exec is created before pausing
9182
id = createExec(c, "test")
9283
dockerCmd(c, "pause", "test")
93-
startExec(id, http.StatusConflict)
84+
startExec(c, id, http.StatusConflict)
9485
dockerCmd(c, "unpause", "test")
95-
startExec(id, http.StatusOK)
86+
startExec(c, id, http.StatusOK)
9687
}
9788

9889
func (s *DockerSuite) TestExecAPIStartBackwardsCompatible(c *check.C) {
@@ -108,6 +99,30 @@ func (s *DockerSuite) TestExecAPIStartBackwardsCompatible(c *check.C) {
10899
c.Assert(resp.StatusCode, checker.Equals, http.StatusOK, comment)
109100
}
110101

102+
// #19362
103+
func (s *DockerSuite) TestExecAPIStartMultipleTimesError(c *check.C) {
104+
dockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
105+
execID := createExec(c, "test")
106+
startExec(c, execID, http.StatusOK)
107+
108+
timeout := time.After(10 * time.Second)
109+
var execJSON struct{ Running bool }
110+
for {
111+
select {
112+
case <-timeout:
113+
c.Fatal("timeout waiting for exec to start")
114+
default:
115+
}
116+
117+
inspectExec(c, execID, &execJSON)
118+
if !execJSON.Running {
119+
break
120+
}
121+
}
122+
123+
startExec(c, execID, http.StatusConflict)
124+
}
125+
111126
func createExec(c *check.C, name string) string {
112127
_, b, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": []string{"true"}})
113128
c.Assert(err, checker.IsNil, check.Commentf(string(b)))
@@ -118,3 +133,22 @@ func createExec(c *check.C, name string) string {
118133
c.Assert(json.Unmarshal(b, &createResp), checker.IsNil, check.Commentf(string(b)))
119134
return createResp.ID
120135
}
136+
137+
func startExec(c *check.C, id string, code int) {
138+
resp, body, err := sockRequestRaw("POST", fmt.Sprintf("/exec/%s/start", id), strings.NewReader(`{"Detach": true}`), "application/json")
139+
c.Assert(err, checker.IsNil)
140+
141+
b, err := readBody(body)
142+
comment := check.Commentf("response body: %s", b)
143+
c.Assert(err, checker.IsNil, comment)
144+
c.Assert(resp.StatusCode, checker.Equals, code, comment)
145+
}
146+
147+
func inspectExec(c *check.C, id string, out interface{}) {
148+
resp, body, err := sockRequestRaw("GET", fmt.Sprintf("/exec/%s/json", id), nil, "")
149+
c.Assert(err, checker.IsNil)
150+
defer body.Close()
151+
c.Assert(resp.StatusCode, checker.Equals, http.StatusOK)
152+
err = json.NewDecoder(body).Decode(out)
153+
c.Assert(err, checker.IsNil)
154+
}

0 commit comments

Comments
 (0)