Browse code

libcontainerd: fix leaking container/exec state

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>

Tonis Tiigi authored on 2017/11/14 07:53:56
Showing 3 changed files
... ...
@@ -8,6 +8,8 @@ import (
8 8
 	"fmt"
9 9
 	"io/ioutil"
10 10
 	"net/http"
11
+	"os"
12
+	"strings"
11 13
 	"time"
12 14
 
13 15
 	"github.com/docker/docker/api/types"
... ...
@@ -198,6 +200,45 @@ func (s *DockerSuite) TestExecAPIStartInvalidCommand(c *check.C) {
198 198
 	c.Assert(inspectJSON.ExecIDs, checker.IsNil)
199 199
 }
200 200
 
201
+func (s *DockerSuite) TestExecStateCleanup(c *check.C) {
202
+	testRequires(c, DaemonIsLinux, SameHostDaemon)
203
+
204
+	// This test checks accidental regressions. Not part of stable API.
205
+
206
+	name := "exec_cleanup"
207
+	cid, _ := dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
208
+	cid = strings.TrimSpace(cid)
209
+
210
+	stateDir := "/var/run/docker/containerd/" + cid
211
+
212
+	checkReadDir := func(c *check.C) (interface{}, check.CommentInterface) {
213
+		fi, err := ioutil.ReadDir(stateDir)
214
+		c.Assert(err, checker.IsNil)
215
+		return len(fi), nil
216
+	}
217
+
218
+	fi, err := ioutil.ReadDir(stateDir)
219
+	c.Assert(err, checker.IsNil)
220
+	c.Assert(len(fi), checker.GreaterThan, 1)
221
+
222
+	id := createExecCmd(c, name, "ls")
223
+	startExec(c, id, http.StatusOK)
224
+	waitForExec(c, id)
225
+
226
+	waitAndAssert(c, 5*time.Second, checkReadDir, checker.Equals, len(fi))
227
+
228
+	id = createExecCmd(c, name, "invalid")
229
+	startExec(c, id, http.StatusBadRequest)
230
+	waitForExec(c, id)
231
+
232
+	waitAndAssert(c, 5*time.Second, checkReadDir, checker.Equals, len(fi))
233
+
234
+	dockerCmd(c, "stop", name)
235
+	_, err = os.Stat(stateDir)
236
+	c.Assert(err, checker.NotNil)
237
+	c.Assert(os.IsNotExist(err), checker.True)
238
+}
239
+
201 240
 func createExec(c *check.C, name string) string {
202 241
 	return createExecCmd(c, name, "true")
203 242
 }
... ...
@@ -28,7 +28,7 @@ import (
28 28
 	"github.com/containerd/typeurl"
29 29
 	"github.com/docker/docker/pkg/ioutils"
30 30
 	"github.com/opencontainers/image-spec/specs-go/v1"
31
-	"github.com/opencontainers/runtime-spec/specs-go"
31
+	specs "github.com/opencontainers/runtime-spec/specs-go"
32 32
 	"github.com/pkg/errors"
33 33
 	"github.com/sirupsen/logrus"
34 34
 )
... ...
@@ -202,7 +202,8 @@ func (c *client) Start(ctx context.Context, id, checkpointDir string, withStdin
202 202
 	uid, gid := getSpecUser(spec)
203 203
 	t, err = ctr.ctr.NewTask(ctx,
204 204
 		func(id string) (containerd.IO, error) {
205
-			cio, err = c.createIO(ctr.bundleDir, id, InitProcessName, stdinCloseSync, withStdin, spec.Process.Terminal, attachStdio)
205
+			fifos := newFIFOSet(ctr.bundleDir, id, InitProcessName, withStdin, spec.Process.Terminal)
206
+			cio, err = c.createIO(fifos, id, InitProcessName, stdinCloseSync, attachStdio)
206 207
 			return cio, err
207 208
 		},
208 209
 		func(_ context.Context, _ *containerd.Client, info *containerd.TaskInfo) error {
... ...
@@ -260,17 +261,21 @@ func (c *client) Exec(ctx context.Context, containerID, processID string, spec *
260 260
 		err            error
261 261
 		stdinCloseSync = make(chan struct{})
262 262
 	)
263
+
264
+	fifos := newFIFOSet(ctr.bundleDir, containerID, processID, withStdin, spec.Terminal)
265
+
263 266
 	defer func() {
264 267
 		if err != nil {
265 268
 			if cio != nil {
266 269
 				cio.Cancel()
267 270
 				cio.Close()
268 271
 			}
272
+			rmFIFOSet(fifos)
269 273
 		}
270 274
 	}()
271 275
 
272 276
 	p, err = ctr.task.Exec(ctx, processID, spec, func(id string) (containerd.IO, error) {
273
-		cio, err = c.createIO(ctr.bundleDir, containerID, processID, stdinCloseSync, withStdin, spec.Terminal, attachStdio)
277
+		cio, err = c.createIO(fifos, containerID, processID, stdinCloseSync, attachStdio)
274 278
 		return cio, err
275 279
 	})
276 280
 	if err != nil {
... ...
@@ -441,7 +446,7 @@ func (c *client) Delete(ctx context.Context, containerID string) error {
441 441
 		return err
442 442
 	}
443 443
 
444
-	if os.Getenv("LIBCONTAINERD_NOCLEAN") == "1" {
444
+	if os.Getenv("LIBCONTAINERD_NOCLEAN") != "1" {
445 445
 		if err := os.RemoveAll(ctr.bundleDir); err != nil {
446 446
 			c.logger.WithError(err).WithFields(logrus.Fields{
447 447
 				"container": containerID,
... ...
@@ -562,8 +567,7 @@ func (c *client) getProcess(containerID, processID string) (containerd.Process,
562 562
 
563 563
 // createIO creates the io to be used by a process
564 564
 // This needs to get a pointer to interface as upon closure the process may not have yet been registered
565
-func (c *client) createIO(bundleDir, containerID, processID string, stdinCloseSync chan struct{}, withStdin, withTerminal bool, attachStdio StdioCallback) (containerd.IO, error) {
566
-	fifos := newFIFOSet(bundleDir, containerID, processID, withStdin, withTerminal)
565
+func (c *client) createIO(fifos *containerd.FIFOSet, containerID, processID string, stdinCloseSync chan struct{}, attachStdio StdioCallback) (containerd.IO, error) {
567 566
 	io, err := newIOPipe(fifos)
568 567
 	if err != nil {
569 568
 		return nil, err
... ...
@@ -639,6 +643,14 @@ func (c *client) processEvent(ctr *container, et EventType, ei EventInfo) {
639 639
 			c.Lock()
640 640
 			delete(ctr.execs, ei.ProcessID)
641 641
 			c.Unlock()
642
+			ctr := c.getContainer(ei.ContainerID)
643
+			if ctr == nil {
644
+				c.logger.WithFields(logrus.Fields{
645
+					"container": ei.ContainerID,
646
+				}).Error("failed to find container")
647
+			} else {
648
+				rmFIFOSet(newFIFOSet(ctr.bundleDir, ei.ContainerID, ei.ProcessID, true, false))
649
+			}
642 650
 		}
643 651
 	})
644 652
 }
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"github.com/containerd/containerd"
11 11
 	"github.com/docker/docker/pkg/idtools"
12 12
 	specs "github.com/opencontainers/runtime-spec/specs-go"
13
+	"github.com/sirupsen/logrus"
13 14
 )
14 15
 
15 16
 func summaryFromInterface(i interface{}) (*Summary, error) {
... ...
@@ -94,3 +95,13 @@ func newFIFOSet(bundleDir, containerID, processID string, withStdin, withTermina
94 94
 
95 95
 	return fifos
96 96
 }
97
+
98
+func rmFIFOSet(fset *containerd.FIFOSet) {
99
+	for _, fn := range []string{fset.Out, fset.In, fset.Err} {
100
+		if fn != "" {
101
+			if err := os.RemoveAll(fn); err != nil {
102
+				logrus.Warnf("libcontainerd: failed to remove fifo %v: %v", fn, err)
103
+			}
104
+		}
105
+	}
106
+}