Browse code

Allow stopping of paused container

When a container is paused, signals are sent once the container has been
unpaused.
Instead of forcing the user to unpause a container before they can ever
send a signal, allow the user to send the signals, and in the case of a
stop signal, automatically unpause the container afterwards.

This is much safer than unpausing the container first then sending a
signal (what a user is currently forced to do), as the container may be
paused for very good reasons and should not be unpaused except for
stopping.
Note that not even SIGKILL is possible while a process is paused,
but it is killed the instant it is unpaused.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2017/07/09 22:34:14
Showing 4 changed files
... ...
@@ -43,7 +43,6 @@ import (
43 43
 	"github.com/docker/docker/pkg/idtools"
44 44
 	"github.com/docker/docker/pkg/plugingetter"
45 45
 	"github.com/docker/docker/pkg/registrar"
46
-	"github.com/docker/docker/pkg/signal"
47 46
 	"github.com/docker/docker/pkg/sysinfo"
48 47
 	"github.com/docker/docker/pkg/system"
49 48
 	"github.com/docker/docker/pkg/truncindex"
... ...
@@ -838,42 +837,7 @@ func (daemon *Daemon) waitForStartupDone() {
838 838
 
839 839
 func (daemon *Daemon) shutdownContainer(c *container.Container) error {
840 840
 	stopTimeout := c.StopTimeout()
841
-	// TODO(windows): Handle docker restart with paused containers
842
-	if c.IsPaused() {
843
-		// To terminate a process in freezer cgroup, we should send
844
-		// SIGTERM to this process then unfreeze it, and the process will
845
-		// force to terminate immediately.
846
-		logrus.Debugf("Found container %s is paused, sending SIGTERM before unpausing it", c.ID)
847
-		sig, ok := signal.SignalMap["TERM"]
848
-		if !ok {
849
-			return errors.New("System does not support SIGTERM")
850
-		}
851
-		if err := daemon.kill(c, int(sig)); err != nil {
852
-			return fmt.Errorf("sending SIGTERM to container %s with error: %v", c.ID, err)
853
-		}
854
-		if err := daemon.containerUnpause(c); err != nil {
855
-			return fmt.Errorf("Failed to unpause container %s with error: %v", c.ID, err)
856
-		}
857
-
858
-		ctx, cancel := context.WithTimeout(context.Background(), time.Duration(stopTimeout)*time.Second)
859
-		defer cancel()
860 841
 
861
-		// Wait with timeout for container to exit.
862
-		if status := <-c.Wait(ctx, container.WaitConditionNotRunning); status.Err() != nil {
863
-			logrus.Debugf("container %s failed to exit in %d second of SIGTERM, sending SIGKILL to force", c.ID, stopTimeout)
864
-			sig, ok := signal.SignalMap["KILL"]
865
-			if !ok {
866
-				return errors.New("System does not support SIGKILL")
867
-			}
868
-			if err := daemon.kill(c, int(sig)); err != nil {
869
-				logrus.Errorf("Failed to SIGKILL container %s", c.ID)
870
-			}
871
-			// Wait for exit again without a timeout.
872
-			// Explicitly ignore the result.
873
-			_ = <-c.Wait(context.Background(), container.WaitConditionNotRunning)
874
-			return status.Err()
875
-		}
876
-	}
877 842
 	// If container failed to exit in stopTimeout seconds of SIGTERM, then using the force
878 843
 	if err := daemon.containerStop(c, stopTimeout); err != nil {
879 844
 		return fmt.Errorf("Failed to stop container %s with error: %v", c.ID, err)
... ...
@@ -60,15 +60,11 @@ func (daemon *Daemon) killWithSignal(container *containerpkg.Container, sig int)
60 60
 	container.Lock()
61 61
 	defer container.Unlock()
62 62
 
63
-	// We could unpause the container for them rather than returning this error
64
-	if container.Paused {
65
-		return fmt.Errorf("Container %s is paused. Unpause the container before stopping or killing", container.ID)
66
-	}
67
-
68 63
 	if !container.Running {
69 64
 		return errNotRunning{container.ID}
70 65
 	}
71 66
 
67
+	var unpause bool
72 68
 	if container.Config.StopSignal != "" && syscall.Signal(sig) != syscall.SIGKILL {
73 69
 		containerStopSignal, err := signal.ParseSignal(container.Config.StopSignal)
74 70
 		if err != nil {
... ...
@@ -76,9 +72,11 @@ func (daemon *Daemon) killWithSignal(container *containerpkg.Container, sig int)
76 76
 		}
77 77
 		if containerStopSignal == syscall.Signal(sig) {
78 78
 			container.ExitOnNext()
79
+			unpause = container.Paused
79 80
 		}
80 81
 	} else {
81 82
 		container.ExitOnNext()
83
+		unpause = container.Paused
82 84
 	}
83 85
 
84 86
 	if !daemon.IsShuttingDown() {
... ...
@@ -98,11 +96,19 @@ func (daemon *Daemon) killWithSignal(container *containerpkg.Container, sig int)
98 98
 		if strings.Contains(err.Error(), "container not found") ||
99 99
 			strings.Contains(err.Error(), "no such process") {
100 100
 			logrus.Warnf("container kill failed because of 'container not found' or 'no such process': %s", err.Error())
101
+			unpause = false
101 102
 		} else {
102 103
 			return err
103 104
 		}
104 105
 	}
105 106
 
107
+	if unpause {
108
+		// above kill signal will be sent once resume is finished
109
+		if err := daemon.containerd.Resume(container.ID); err != nil {
110
+			logrus.Warn("Cannot unpause container %s: %s", container.ID, err)
111
+		}
112
+	}
113
+
106 114
 	attributes := map[string]string{
107 115
 		"signal": fmt.Sprintf("%d", sig),
108 116
 	}
... ...
@@ -2,6 +2,7 @@ package main
2 2
 
3 3
 import (
4 4
 	"strings"
5
+	"time"
5 6
 
6 7
 	"github.com/docker/docker/integration-cli/checker"
7 8
 	"github.com/docker/docker/integration-cli/cli"
... ...
@@ -65,3 +66,13 @@ func (s *DockerSuite) TestPauseFailsOnWindowsServerContainers(c *check.C) {
65 65
 	out, _, _ := dockerCmdWithError("pause", "test")
66 66
 	c.Assert(out, checker.Contains, "cannot pause Windows Server Containers")
67 67
 }
68
+
69
+func (s *DockerSuite) TestStopPausedContainer(c *check.C) {
70
+	testRequires(c, DaemonIsLinux)
71
+
72
+	id := runSleepingContainer(c)
73
+	cli.WaitRun(c, id)
74
+	cli.DockerCmd(c, "pause", id)
75
+	cli.DockerCmd(c, "stop", id)
76
+	cli.WaitForInspectResult(c, id, "{{.State.Running}}", "false", 30*time.Second)
77
+}
... ...
@@ -396,7 +396,7 @@ func runSleepingContainerInImage(c *check.C, image string, extraArgs ...string)
396 396
 	args = append(args, extraArgs...)
397 397
 	args = append(args, image)
398 398
 	args = append(args, sleepCommandForDaemonPlatform()...)
399
-	return cli.DockerCmd(c, args...).Combined()
399
+	return strings.TrimSpace(cli.DockerCmd(c, args...).Combined())
400 400
 }
401 401
 
402 402
 // minimalBaseImage returns the name of the minimal base image for the current