Browse code

Add config parameter to change per-container stop timeout during daemon shutdown

This fix tries to add a flag `--stop-timeout` to specify the timeout value
(in seconds) for the container to stop before SIGKILL is issued. If stop timeout
is not specified then the default timeout (10s) is used.

Additional test cases have been added to cover the change.

This fix is related to #22471. Another pull request will add `--shutdown-timeout`
to daemon for #22471.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

Yong Tang authored on 2016/05/27 05:34:48
Showing 11 changed files
... ...
@@ -44,6 +44,11 @@ import (
44 44
 
45 45
 const configFileName = "config.v2.json"
46 46
 
47
+const (
48
+	// defaultStopTimeout is the timeout (in seconds) for the syscall signal used to stop a container.
49
+	defaultStopTimeout = 10
50
+)
51
+
47 52
 var (
48 53
 	errInvalidEndpoint = fmt.Errorf("invalid endpoint while building port map info")
49 54
 	errInvalidNetwork  = fmt.Errorf("invalid network settings while building port map info")
... ...
@@ -578,6 +583,14 @@ func (container *Container) StopSignal() int {
578 578
 	return int(stopSignal)
579 579
 }
580 580
 
581
+// StopTimeout returns the timeout (in seconds) used to stop the container.
582
+func (container *Container) StopTimeout() int {
583
+	if container.Config.StopTimeout != nil {
584
+		return *container.Config.StopTimeout
585
+	}
586
+	return defaultStopTimeout
587
+}
588
+
581 589
 // InitDNSHostConfig ensures that the dns fields are never nil.
582 590
 // New containers don't ever have those fields nil,
583 591
 // but pre created containers can still have those nil values.
... ...
@@ -34,3 +34,27 @@ func TestContainerStopSignal(t *testing.T) {
34 34
 		t.Fatalf("Expected 9, got %v", s)
35 35
 	}
36 36
 }
37
+
38
+func TestContainerStopTimeout(t *testing.T) {
39
+	c := &Container{
40
+		CommonContainer: CommonContainer{
41
+			Config: &container.Config{},
42
+		},
43
+	}
44
+
45
+	s := c.StopTimeout()
46
+	if s != defaultStopTimeout {
47
+		t.Fatalf("Expected %v, got %v", defaultStopTimeout, s)
48
+	}
49
+
50
+	stopTimeout := 15
51
+	c = &Container{
52
+		CommonContainer: CommonContainer{
53
+			Config: &container.Config{StopTimeout: &stopTimeout},
54
+		},
55
+	}
56
+	s = c.StopSignal()
57
+	if s != 15 {
58
+		t.Fatalf("Expected 15, got %v", s)
59
+	}
60
+}
... ...
@@ -708,8 +708,8 @@ func (daemon *Daemon) shutdownContainer(c *container.Container) error {
708 708
 		if err := daemon.containerUnpause(c); err != nil {
709 709
 			return fmt.Errorf("Failed to unpause container %s with error: %v", c.ID, err)
710 710
 		}
711
-		if _, err := c.WaitStop(10 * time.Second); err != nil {
712
-			logrus.Debugf("container %s failed to exit in 10 seconds of SIGTERM, sending SIGKILL to force", c.ID)
711
+		if _, err := c.WaitStop(time.Duration(c.StopTimeout()) * time.Second); err != nil {
712
+			logrus.Debugf("container %s failed to exit in %d second of SIGTERM, sending SIGKILL to force", c.ID, c.StopTimeout())
713 713
 			sig, ok := signal.SignalMap["KILL"]
714 714
 			if !ok {
715 715
 				return fmt.Errorf("System does not support SIGKILL")
... ...
@@ -721,8 +721,8 @@ func (daemon *Daemon) shutdownContainer(c *container.Container) error {
721 721
 			return err
722 722
 		}
723 723
 	}
724
-	// If container failed to exit in 10 seconds of SIGTERM, then using the force
725
-	if err := daemon.containerStop(c, 10); err != nil {
724
+	// If container failed to exit in c.StopTimeout() seconds of SIGTERM, then using the force
725
+	if err := daemon.containerStop(c, c.StopTimeout()); err != nil {
726 726
 		return fmt.Errorf("Failed to stop container %s with error: %v", c.ID, err)
727 727
 	}
728 728
 
... ...
@@ -128,6 +128,7 @@ This section lists each version from latest to oldest.  Each listing includes a
128 128
 * `DELETE /containers/(name)` endpoint now returns an error of `removal of container name is already in progress` with status code of 400, when container name is in a state of removal in progress.
129 129
 * `GET /containers/json` now supports a `is-task` filter to filter
130 130
   containers that are tasks (part of a service in swarm mode).
131
+* `POST /containers/create` now takes `StopTimeout` field.
131 132
 
132 133
 ### v1.24 API changes
133 134
 
... ...
@@ -284,6 +284,7 @@ Create a container
284 284
                    "22/tcp": {}
285 285
            },
286 286
            "StopSignal": "SIGTERM",
287
+           "StopTimeout": 10,
287 288
            "HostConfig": {
288 289
              "Binds": ["/tmp:/tmp"],
289 290
              "Links": ["redis3:redis"],
... ...
@@ -391,6 +392,7 @@ Create a container
391 391
 -   **ExposedPorts** - An object mapping ports to an empty object in the form of:
392 392
       `"ExposedPorts": { "<port>/<tcp|udp>: {}" }`
393 393
 -   **StopSignal** - Signal to stop a container as a string or unsigned integer. `SIGTERM` by default.
394
+-   **StopTimeout** - Timeout (in seconds) to stop a container. 10 by default.
394 395
 -   **HostConfig**
395 396
     -   **Binds** – A list of volume bindings for this container. Each volume binding is a string in one of these forms:
396 397
            + `host-src:container-dest` to bind-mount a host path into the
... ...
@@ -580,7 +582,8 @@ Return low-level information on the container `id`
580 580
 				"/volumes/data": {}
581 581
 			},
582 582
 			"WorkingDir": "",
583
-			"StopSignal": "SIGTERM"
583
+			"StopSignal": "SIGTERM",
584
+			"StopTimeout": 10
584 585
 		},
585 586
 		"Created": "2015-01-06T15:47:31.485331387Z",
586 587
 		"Driver": "devicemapper",
... ...
@@ -94,6 +94,7 @@ Options:
94 94
                                     Unit is optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes),
95 95
                                     or `g` (gigabytes). If you omit the unit, the system uses bytes.
96 96
       --stop-signal string          Signal to stop a container, SIGTERM by default (default "SIGTERM")
97
+      --stop-timeout=10             Timeout (in seconds) to stop a container
97 98
       --storage-opt value           Storage driver options for the container (default [])
98 99
       --sysctl value                Sysctl options (default map[])
99 100
       --tmpfs value                 Mount a tmpfs directory (default [])
... ...
@@ -101,6 +101,7 @@ Options:
101 101
                                     or `g` (gigabytes). If you omit the unit, the system uses bytes.
102 102
       --sig-proxy                   Proxy received signals to the process (default true)
103 103
       --stop-signal string          Signal to stop a container, SIGTERM by default (default "SIGTERM")
104
+      --stop-timeout=10             Timeout (in seconds) to stop a container
104 105
       --storage-opt value           Storage driver options for the container (default [])
105 106
       --sysctl value                Sysctl options (default map[])
106 107
       --tmpfs value                 Mount a tmpfs directory (default [])
... ...
@@ -620,6 +621,11 @@ or a signal name in the format SIGNAME, for instance SIGKILL.
620 620
 On Windows, this flag can be used to specify the `credentialspec` option. 
621 621
 The `credentialspec` must be in the format `file://spec.txt` or `registry://keyname`. 
622 622
 
623
+### Stop container with timeout (--stop-timeout)
624
+
625
+The `--stop-timeout` flag sets the the timeout (in seconds) that a pre-defined (see `--stop-signal`) system call
626
+signal that will be sent to the container to exit. After timeout elapses the container will be killed with SIGKILL.
627
+
623 628
 ### Specify isolation technology for container (--isolation)
624 629
 
625 630
 This option is useful in situations where you are running Docker containers on
... ...
@@ -496,3 +496,18 @@ exec "$@"`,
496 496
 	out, _ = dockerCmd(c, "start", "-a", id)
497 497
 	c.Assert(strings.TrimSpace(out), check.Equals, "foo")
498 498
 }
499
+
500
+// #22471
501
+func (s *DockerSuite) TestCreateStopTimeout(c *check.C) {
502
+	name1 := "test_create_stop_timeout_1"
503
+	dockerCmd(c, "create", "--name", name1, "--stop-timeout", "15", "busybox")
504
+
505
+	res := inspectFieldJSON(c, name1, "Config.StopTimeout")
506
+	c.Assert(res, checker.Contains, "15")
507
+
508
+	name2 := "test_create_stop_timeout_2"
509
+	dockerCmd(c, "create", "--name", name2, "busybox")
510
+
511
+	res = inspectFieldJSON(c, name2, "Config.StopTimeout")
512
+	c.Assert(res, checker.Contains, "null")
513
+}
... ...
@@ -68,6 +68,7 @@ docker-create - Create a new container
68 68
 [**--security-opt**[=*[]*]]
69 69
 [**--storage-opt**[=*[]*]]
70 70
 [**--stop-signal**[=*SIGNAL*]]
71
+[**--stop-timeout**[=*TIMEOUT*]]
71 72
 [**--shm-size**[=*[]*]]
72 73
 [**--sysctl**[=*[]*]]
73 74
 [**-t**|**--tty**]
... ...
@@ -352,6 +353,9 @@ unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap.
352 352
 **--stop-signal**=*SIGTERM*
353 353
   Signal to stop a container. Default is SIGTERM.
354 354
 
355
+**--stop-timeout**=*10*
356
+  Timeout (in seconds) to stop a container. Default is 10.
357
+
355 358
 **--sysctl**=SYSCTL
356 359
   Configure namespaced kernel parameters at runtime
357 360
 
... ...
@@ -70,6 +70,7 @@ docker-run - Run a command in a new container
70 70
 [**--security-opt**[=*[]*]]
71 71
 [**--storage-opt**[=*[]*]]
72 72
 [**--stop-signal**[=*SIGNAL*]]
73
+[**--stop-timeout**[=*TIMEOUT*]]
73 74
 [**--shm-size**[=*[]*]]
74 75
 [**--sig-proxy**[=*true*]]
75 76
 [**--sysctl**[=*[]*]]
... ...
@@ -502,6 +503,9 @@ incompatible with any restart policy other than `none`.
502 502
 **--stop-signal**=*SIGTERM*
503 503
   Signal to stop a container. Default is SIGTERM.
504 504
 
505
+**--stop-timeout**=*10*
506
+  Timeout (in seconds) to stop a container. Default is 10.
507
+
505 508
 **--shm-size**=""
506 509
    Size of `/dev/shm`. The format is `<number><unit>`.
507 510
    `number` must be greater than `0`.  Unit is optional and can be `b` (bytes), `k` (kilobytes), `m`(megabytes), or `g` (gigabytes).
... ...
@@ -106,6 +106,7 @@ type ContainerOptions struct {
106 106
 	init              bool
107 107
 	initPath          string
108 108
 	credentialSpec    string
109
+	stopTimeout       int
109 110
 
110 111
 	Image string
111 112
 	Args  []string
... ...
@@ -145,6 +146,7 @@ func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
145 145
 		ulimits:           NewUlimitOpt(nil),
146 146
 		volumes:           opts.NewListOpts(nil),
147 147
 		volumesFrom:       opts.NewListOpts(nil),
148
+		stopSignal:        flags.String("stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal)),
148 149
 	}
149 150
 
150 151
 	// General purpose flags
... ...
@@ -558,6 +560,9 @@ func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *c
558 558
 	if flags.Changed("stop-signal") {
559 559
 		config.StopSignal = copts.stopSignal
560 560
 	}
561
+	if flags.Changed("stop-timeout") {
562
+		config.StopTimeout = copts.flStopTimeout
563
+	}
561 564
 
562 565
 	hostConfig := &container.HostConfig{
563 566
 		Binds:           binds,