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>
| ... | ... |
@@ -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 |
| ... | ... |
@@ -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 |
|
| ... | ... |
@@ -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 |
+) |
| ... | ... |
@@ -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{
|