Browse code

Signal to stop a container.

Allow to set the signal to stop a container in `docker run`:
- Use `--stop-signal` with docker-run to set the default signal the container will use to exit.

Signed-off-by: David Calavera <david.calavera@gmail.com>

David Calavera authored on 2015/08/05 05:51:48
Showing 18 changed files
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"net/http"
7 7
 	"strconv"
8 8
 	"strings"
9
+	"syscall"
9 10
 	"time"
10 11
 
11 12
 	"golang.org/x/net/websocket"
... ...
@@ -220,32 +221,18 @@ func (s *Server) postContainersKill(ctx context.Context, w http.ResponseWriter,
220 220
 		return err
221 221
 	}
222 222
 
223
-	var sig uint64
223
+	var sig syscall.Signal
224 224
 	name := vars["name"]
225 225
 
226 226
 	// If we have a signal, look at it. Otherwise, do nothing
227 227
 	if sigStr := r.Form.Get("signal"); sigStr != "" {
228
-		// Check if we passed the signal as a number:
229
-		// The largest legal signal is 31, so let's parse on 5 bits
230
-		sigN, err := strconv.ParseUint(sigStr, 10, 5)
231
-		if err != nil {
232
-			// The signal is not a number, treat it as a string (either like
233
-			// "KILL" or like "SIGKILL")
234
-			syscallSig, ok := signal.SignalMap[strings.TrimPrefix(sigStr, "SIG")]
235
-			if !ok {
236
-				return fmt.Errorf("Invalid signal: %s", sigStr)
237
-			}
238
-			sig = uint64(syscallSig)
239
-		} else {
240
-			sig = sigN
241
-		}
242
-
243
-		if sig == 0 {
244
-			return fmt.Errorf("Invalid signal: %s", sigStr)
228
+		var err error
229
+		if sig, err = signal.ParseSignal(sigStr); err != nil {
230
+			return err
245 231
 		}
246 232
 	}
247 233
 
248
-	if err := s.daemon.ContainerKill(name, sig); err != nil {
234
+	if err := s.daemon.ContainerKill(name, uint64(sig)); err != nil {
249 235
 		_, isStopped := err.(daemon.ErrContainerNotRunning)
250 236
 		// Return error that's not caused because the container is stopped.
251 237
 		// Return error if the container is not running and the api is >= 1.20
... ...
@@ -1149,6 +1149,7 @@ _docker_run() {
1149 1149
 		--publish -p
1150 1150
 		--restart
1151 1151
 		--security-opt
1152
+		--stop-signal
1152 1153
 		--ulimit
1153 1154
 		--user -u
1154 1155
 		--uts
... ...
@@ -335,6 +335,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l restart -d 'Res
335 335
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l rm -d 'Automatically remove the container when it exits (incompatible with -d)'
336 336
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l security-opt -d 'Security Options'
337 337
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l sig-proxy -d 'Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP, and SIGKILL are not proxied.'
338
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l stop-signal 'Signal to kill a container'
338 339
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s t -l tty -d 'Allocate a pseudo-TTY'
339 340
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s u -l user -d 'Username or UID'
340 341
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s v -l volume -d 'Bind mount a volume (e.g., from the host: -v /host:/container, from Docker: -v /container)'
... ...
@@ -502,6 +502,7 @@ __docker_subcommand() {
502 502
                 "($help -d --detach)"{-d,--detach}"[Detached mode: leave the container running in the background]" \
503 503
                 "($help)--rm[Remove intermediate containers when it exits]" \
504 504
                 "($help)--sig-proxy[Proxy all received signals to the process (non-TTY mode only)]" \
505
+                "($help)--stop-signal[Signal to kill a container]" \
505 506
                 "($help -): :__docker_images" \
506 507
                 "($help -):command: _command_names -e" \
507 508
                 "($help -)*::arguments: _normal" && ret=0
... ...
@@ -27,6 +27,7 @@ import (
27 27
 	"github.com/docker/docker/pkg/mount"
28 28
 	"github.com/docker/docker/pkg/nat"
29 29
 	"github.com/docker/docker/pkg/promise"
30
+	"github.com/docker/docker/pkg/signal"
30 31
 	"github.com/docker/docker/pkg/symlink"
31 32
 	"github.com/docker/docker/runconfig"
32 33
 	"github.com/docker/docker/volume"
... ...
@@ -495,10 +496,10 @@ func (container *Container) Kill() error {
495 495
 	return nil
496 496
 }
497 497
 
498
-// Stop halts a container by sending SIGTERM, waiting for the given
498
+// Stop halts a container by sending a stop signal, waiting for the given
499 499
 // duration in seconds, and then calling SIGKILL and waiting for the
500 500
 // process to exit. If a negative duration is given, Stop will wait
501
-// for SIGTERM forever. If the container is not running Stop returns
501
+// for the initial signal forever. If the container is not running Stop returns
502 502
 // immediately.
503 503
 func (container *Container) Stop(seconds int) error {
504 504
 	if !container.IsRunning() {
... ...
@@ -506,9 +507,9 @@ func (container *Container) Stop(seconds int) error {
506 506
 	}
507 507
 
508 508
 	// 1. Send a SIGTERM
509
-	if err := container.killPossiblyDeadProcess(int(syscall.SIGTERM)); err != nil {
509
+	if err := container.killPossiblyDeadProcess(container.stopSignal()); err != nil {
510 510
 		logrus.Infof("Failed to send SIGTERM to the process, force killing")
511
-		if err := container.killPossiblyDeadProcess(int(syscall.SIGKILL)); err != nil {
511
+		if err := container.killPossiblyDeadProcess(9); err != nil {
512 512
 			return err
513 513
 		}
514 514
 	}
... ...
@@ -523,7 +524,7 @@ func (container *Container) Stop(seconds int) error {
523 523
 		}
524 524
 	}
525 525
 
526
-	container.logEvent("stop")
526
+	container.LogEvent("stop")
527 527
 	return nil
528 528
 }
529 529
 
... ...
@@ -1140,3 +1141,15 @@ func (container *Container) copyImagePathContent(v volume.Volume, destination st
1140 1140
 
1141 1141
 	return v.Unmount()
1142 1142
 }
1143
+
1144
+func (container *Container) stopSignal() int {
1145
+	var stopSignal syscall.Signal
1146
+	if container.Config.StopSignal != "" {
1147
+		stopSignal, _ = signal.ParseSignal(container.Config.StopSignal)
1148
+	}
1149
+
1150
+	if int(stopSignal) == 0 {
1151
+		stopSignal, _ = signal.ParseSignal(signal.DefaultStopSignal)
1152
+	}
1153
+	return int(stopSignal)
1154
+}
... ...
@@ -31,3 +31,31 @@ func TestValidContainerNames(t *testing.T) {
31 31
 		}
32 32
 	}
33 33
 }
34
+
35
+func TestContainerStopSignal(t *testing.T) {
36
+	c := &Container{
37
+		CommonContainer: CommonContainer{
38
+			Config: &runconfig.Config{},
39
+		},
40
+	}
41
+
42
+	def, err := signal.ParseSignal(signal.DefaultStopSignal)
43
+	if err != nil {
44
+		t.Fatal(err)
45
+	}
46
+
47
+	s := c.stopSignal()
48
+	if s != int(def) {
49
+		t.Fatalf("Expected %v, got %v", def, s)
50
+	}
51
+
52
+	c = &Container{
53
+		CommonContainer: CommonContainer{
54
+			Config: &runconfig.Config{StopSignal: "SIGKILL"},
55
+		},
56
+	}
57
+	s = c.stopSignal()
58
+	if s != 9 {
59
+		t.Fatalf("Expected 9, got %v", s)
60
+	}
61
+}
... ...
@@ -1095,6 +1095,11 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig,
1095 1095
 		}
1096 1096
 	}
1097 1097
 
1098
+	_, err := signal.ParseSignal(config.StopSignal)
1099
+	if err != nil {
1100
+		return nil, err
1101
+	}
1102
+
1098 1103
 	// Now do platform-specific verification
1099 1104
 	return verifyPlatformContainerSettings(daemon, hostConfig, config)
1100 1105
 }
... ...
@@ -82,6 +82,7 @@ This section lists each version from latest to oldest.  Each listing includes a
82 82
 * `DELETE /volumes/(name)`remove a volume with the specified name.
83 83
 * `VolumeDriver` has been moved from config to hostConfig to make the configuration portable.
84 84
 * `GET /images/(name)/json` now returns information about tags of the image.
85
+* The `config` option now accepts the field `StopSignal`, which specifies the signal to use to kill a container.
85 86
 
86 87
 
87 88
 ### v1.20 API changes
... ...
@@ -315,3 +315,19 @@ func (s *DockerSuite) TestRunWithSwappinessInvalid(c *check.C) {
315 315
 		c.Fatalf("failed. test was able to set invalid value, output: %q", out)
316 316
 	}
317 317
 }
318
+
319
+func (s *DockerSuite) TestStopContainerSignal(c *check.C) {
320
+	out, _ := dockerCmd(c, "run", "--stop-signal", "SIGUSR1", "-d", "busybox", "/bin/sh", "-c", `trap 'echo "exit trapped"; exit 0' USR1; while true; do sleep 1; done`)
321
+	containerID := strings.TrimSpace(out)
322
+
323
+	if err := waitRun(containerID); err != nil {
324
+		c.Fatal(err)
325
+	}
326
+
327
+	dockerCmd(c, "stop", containerID)
328
+	out, _ = dockerCmd(c, "logs", containerID)
329
+
330
+	if !strings.Contains(out, "exit trapped") {
331
+		c.Fatalf("Expected `exit trapped` in the log, got %v", out)
332
+	}
333
+}
... ...
@@ -51,6 +51,7 @@ docker-create - Create a new container
51 51
 [**--read-only**[=*false*]]
52 52
 [**--restart**[=*RESTART*]]
53 53
 [**--security-opt**[=*[]*]]
54
+[**--stop-signal**[=*SIGNAL*]]
54 55
 [**-t**|**--tty**[=*false*]]
55 56
 [**-u**|**--user**[=*USER*]]
56 57
 [**--ulimit**[=*[]*]]
... ...
@@ -239,6 +240,9 @@ This value should always larger than **-m**, so you should always use this with
239 239
 **--security-opt**=[]
240 240
    Security Options
241 241
 
242
+**--stop-signal**=SIGTERM
243
+  Signal to stop a container. Default is SIGTERM.
244
+
242 245
 **-t**, **--tty**=*true*|*false*
243 246
    Allocate a pseudo-TTY. The default is *false*.
244 247
 
... ...
@@ -180,7 +180,8 @@ To get information on a container use its ID or instance name:
180 180
         "Memory": 0,
181 181
         "MemorySwap": 0,
182 182
         "CpuShares": 0,
183
-        "Cpuset": ""
183
+        "Cpuset": "",
184
+        "StopSignal": 15,
184 185
     }
185 186
     }
186 187
     ]
... ...
@@ -53,6 +53,7 @@ docker-run - Run a command in a new container
53 53
 [**--restart**[=*RESTART*]]
54 54
 [**--rm**[=*false*]]
55 55
 [**--security-opt**[=*[]*]]
56
+[**--stop-signal**[=*SIGNAL*]]
56 57
 [**--sig-proxy**[=*true*]]
57 58
 [**-t**|**--tty**[=*false*]]
58 59
 [**-u**|**--user**[=*USER*]]
... ...
@@ -371,7 +372,7 @@ its root filesystem mounted as read only prohibiting any writes.
371 371
 
372 372
 **--restart**="no"
373 373
    Restart policy to apply when a container exits (no, on-failure[:max-retry], always, unless-stopped).
374
-      
374
+
375 375
 **--rm**=*true*|*false*
376 376
    Automatically remove the container when it exits (incompatible with -d). The default is *false*.
377 377
 
... ...
@@ -384,6 +385,9 @@ its root filesystem mounted as read only prohibiting any writes.
384 384
     "label:level:LEVEL" : Set the label level for the container
385 385
     "label:disable"     : Turn off label confinement for the container
386 386
 
387
+**--stop-signal**=SIGTERM
388
+  Signal to stop a container. Default is SIGTERM.
389
+
387 390
 **--sig-proxy**=*true*|*false*
388 391
    Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP, and SIGKILL are not proxied. The default is *true*.
389 392
 
... ...
@@ -19,7 +19,7 @@ Stop a running container (Send SIGTERM, and then SIGKILL after
19 19
   Print usage statement
20 20
 
21 21
 **-t**, **--time**=10
22
-   Number of seconds to wait for the container to stop before killing it. Default is 10 seconds.
22
+  Number of seconds to wait for the container to stop before killing it. Default is 10 seconds.
23 23
 
24 24
 #See also
25 25
 **docker-start(1)** to restart a stopped container.
... ...
@@ -3,8 +3,12 @@
3 3
 package signal
4 4
 
5 5
 import (
6
+	"fmt"
6 7
 	"os"
7 8
 	"os/signal"
9
+	"strconv"
10
+	"strings"
11
+	"syscall"
8 12
 )
9 13
 
10 14
 // CatchAll catches all signals and relays them to the specified channel.
... ...
@@ -21,3 +25,20 @@ func StopCatch(sigc chan os.Signal) {
21 21
 	signal.Stop(sigc)
22 22
 	close(sigc)
23 23
 }
24
+
25
+// ParseSignal translates a string to a valid syscall signal.
26
+// It returns an error if the signal map doesn't include the given signal.
27
+func ParseSignal(rawSignal string) (syscall.Signal, error) {
28
+	s, err := strconv.Atoi(rawSignal)
29
+	if err == nil {
30
+		if s == 0 {
31
+			return -1, fmt.Errorf("Invalid signal: %s", rawSignal)
32
+		}
33
+		return syscall.Signal(s), nil
34
+	}
35
+	signal, ok := SignalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")]
36
+	if !ok {
37
+		return -1, fmt.Errorf("Invalid signal: %s", rawSignal)
38
+	}
39
+	return signal, nil
40
+}
... ...
@@ -9,8 +9,11 @@ import (
9 9
 // Signals used in api/client (no windows equivalent, use
10 10
 // invalid signals so they don't get handled)
11 11
 
12
-// SIGCHLD is a signal sent to a process when a child process terminates, is interrupted, or resumes after being interrupted.
13
-const SIGCHLD = syscall.SIGCHLD
14
-
15
-// SIGWINCH is a signal sent to a process when its controlling terminal changes its size
16
-const SIGWINCH = syscall.SIGWINCH
12
+const (
13
+	// SIGCHLD is a signal sent to a process when a child process terminates, is interrupted, or resumes after being interrupted.
14
+	SIGCHLD = syscall.SIGCHLD
15
+	// SIGWINCH is a signal sent to a process when its controlling terminal changes its size
16
+	SIGWINCH = syscall.SIGWINCH
17
+	// DefaultStopSignal is the syscall signal used to stop a container in unix systems.
18
+	DefaultStopSignal = "SIGTERM"
19
+)
... ...
@@ -11,4 +11,6 @@ import (
11 11
 const (
12 12
 	SIGCHLD  = syscall.Signal(0xff)
13 13
 	SIGWINCH = syscall.Signal(0xff)
14
+	// DefaultStopSignal is the syscall signal used to stop a container in windows systems.
15
+	DefaultStopSignal = "15"
14 16
 )
... ...
@@ -34,6 +34,7 @@ type Config struct {
34 34
 	MacAddress      string                // Mac Address of the container
35 35
 	OnBuild         []string              // ONBUILD metadata that were defined on the image Dockerfile
36 36
 	Labels          map[string]string     // List of labels set to this container
37
+	StopSignal      string                // Signal to stop a container
37 38
 }
38 39
 
39 40
 // DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	flag "github.com/docker/docker/pkg/mflag"
10 10
 	"github.com/docker/docker/pkg/nat"
11 11
 	"github.com/docker/docker/pkg/parsers"
12
+	"github.com/docker/docker/pkg/signal"
12 13
 	"github.com/docker/docker/pkg/stringutils"
13 14
 	"github.com/docker/docker/pkg/units"
14 15
 )
... ...
@@ -93,6 +94,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
93 93
 		flLoggingDriver   = cmd.String([]string{"-log-driver"}, "", "Logging driver for container")
94 94
 		flCgroupParent    = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
95 95
 		flVolumeDriver    = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
96
+		flStopSignal      = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
96 97
 	)
97 98
 
98 99
 	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
... ...
@@ -322,6 +324,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
322 322
 		Entrypoint:      entrypoint,
323 323
 		WorkingDir:      *flWorkingDir,
324 324
 		Labels:          convertKVStringsToMap(labels),
325
+		StopSignal:      *flStopSignal,
325 326
 	}
326 327
 
327 328
 	hostConfig := &HostConfig{