Forráskód Böngészése

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 10 éve
szülő
commit
0e50d946a2

+ 6 - 19
api/server/container.go

@@ -6,6 +6,7 @@ import (
 	"net/http"
 	"net/http"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
+	"syscall"
 	"time"
 	"time"
 
 
 	"golang.org/x/net/websocket"
 	"golang.org/x/net/websocket"
@@ -220,32 +221,18 @@ func (s *Server) postContainersKill(ctx context.Context, w http.ResponseWriter,
 		return err
 		return err
 	}
 	}
 
 
-	var sig uint64
+	var sig syscall.Signal
 	name := vars["name"]
 	name := vars["name"]
 
 
 	// If we have a signal, look at it. Otherwise, do nothing
 	// If we have a signal, look at it. Otherwise, do nothing
 	if sigStr := r.Form.Get("signal"); sigStr != "" {
 	if sigStr := r.Form.Get("signal"); sigStr != "" {
-		// Check if we passed the signal as a number:
-		// The largest legal signal is 31, so let's parse on 5 bits
-		sigN, err := strconv.ParseUint(sigStr, 10, 5)
-		if err != nil {
-			// The signal is not a number, treat it as a string (either like
-			// "KILL" or like "SIGKILL")
-			syscallSig, ok := signal.SignalMap[strings.TrimPrefix(sigStr, "SIG")]
-			if !ok {
-				return fmt.Errorf("Invalid signal: %s", sigStr)
-			}
-			sig = uint64(syscallSig)
-		} else {
-			sig = sigN
-		}
-
-		if sig == 0 {
-			return fmt.Errorf("Invalid signal: %s", sigStr)
+		var err error
+		if sig, err = signal.ParseSignal(sigStr); err != nil {
+			return err
 		}
 		}
 	}
 	}
 
 
-	if err := s.daemon.ContainerKill(name, sig); err != nil {
+	if err := s.daemon.ContainerKill(name, uint64(sig)); err != nil {
 		_, isStopped := err.(daemon.ErrContainerNotRunning)
 		_, isStopped := err.(daemon.ErrContainerNotRunning)
 		// Return error that's not caused because the container is stopped.
 		// Return error that's not caused because the container is stopped.
 		// Return error if the container is not running and the api is >= 1.20
 		// Return error if the container is not running and the api is >= 1.20

+ 1 - 0
contrib/completion/bash/docker

@@ -1149,6 +1149,7 @@ _docker_run() {
 		--publish -p
 		--publish -p
 		--restart
 		--restart
 		--security-opt
 		--security-opt
+		--stop-signal
 		--ulimit
 		--ulimit
 		--user -u
 		--user -u
 		--uts
 		--uts

+ 1 - 0
contrib/completion/fish/docker.fish

@@ -335,6 +335,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l restart -d 'Res
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l rm -d 'Automatically remove the container when it exits (incompatible with -d)'
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l rm -d 'Automatically remove the container when it exits (incompatible with -d)'
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l security-opt -d 'Security Options'
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l security-opt -d 'Security Options'
 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.'
 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.'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l stop-signal 'Signal to kill a container'
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s t -l tty -d 'Allocate a pseudo-TTY'
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s t -l tty -d 'Allocate a pseudo-TTY'
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s u -l user -d 'Username or UID'
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s u -l user -d 'Username or UID'
 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)'
 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)'

+ 1 - 0
contrib/completion/zsh/_docker

@@ -502,6 +502,7 @@ __docker_subcommand() {
                 "($help -d --detach)"{-d,--detach}"[Detached mode: leave the container running in the background]" \
                 "($help -d --detach)"{-d,--detach}"[Detached mode: leave the container running in the background]" \
                 "($help)--rm[Remove intermediate containers when it exits]" \
                 "($help)--rm[Remove intermediate containers when it exits]" \
                 "($help)--sig-proxy[Proxy all received signals to the process (non-TTY mode only)]" \
                 "($help)--sig-proxy[Proxy all received signals to the process (non-TTY mode only)]" \
+                "($help)--stop-signal[Signal to kill a container]" \
                 "($help -): :__docker_images" \
                 "($help -): :__docker_images" \
                 "($help -):command: _command_names -e" \
                 "($help -):command: _command_names -e" \
                 "($help -)*::arguments: _normal" && ret=0
                 "($help -)*::arguments: _normal" && ret=0

+ 18 - 5
daemon/container.go

@@ -27,6 +27,7 @@ import (
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/nat"
 	"github.com/docker/docker/pkg/nat"
 	"github.com/docker/docker/pkg/promise"
 	"github.com/docker/docker/pkg/promise"
+	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/symlink"
 	"github.com/docker/docker/pkg/symlink"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
@@ -495,10 +496,10 @@ func (container *Container) Kill() error {
 	return nil
 	return nil
 }
 }
 
 
-// Stop halts a container by sending SIGTERM, waiting for the given
+// Stop halts a container by sending a stop signal, waiting for the given
 // duration in seconds, and then calling SIGKILL and waiting for the
 // duration in seconds, and then calling SIGKILL and waiting for the
 // process to exit. If a negative duration is given, Stop will wait
 // process to exit. If a negative duration is given, Stop will wait
-// for SIGTERM forever. If the container is not running Stop returns
+// for the initial signal forever. If the container is not running Stop returns
 // immediately.
 // immediately.
 func (container *Container) Stop(seconds int) error {
 func (container *Container) Stop(seconds int) error {
 	if !container.IsRunning() {
 	if !container.IsRunning() {
@@ -506,9 +507,9 @@ func (container *Container) Stop(seconds int) error {
 	}
 	}
 
 
 	// 1. Send a SIGTERM
 	// 1. Send a SIGTERM
-	if err := container.killPossiblyDeadProcess(int(syscall.SIGTERM)); err != nil {
+	if err := container.killPossiblyDeadProcess(container.stopSignal()); err != nil {
 		logrus.Infof("Failed to send SIGTERM to the process, force killing")
 		logrus.Infof("Failed to send SIGTERM to the process, force killing")
-		if err := container.killPossiblyDeadProcess(int(syscall.SIGKILL)); err != nil {
+		if err := container.killPossiblyDeadProcess(9); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
@@ -523,7 +524,7 @@ func (container *Container) Stop(seconds int) error {
 		}
 		}
 	}
 	}
 
 
-	container.logEvent("stop")
+	container.LogEvent("stop")
 	return nil
 	return nil
 }
 }
 
 
@@ -1140,3 +1141,15 @@ func (container *Container) copyImagePathContent(v volume.Volume, destination st
 
 
 	return v.Unmount()
 	return v.Unmount()
 }
 }
+
+func (container *Container) stopSignal() int {
+	var stopSignal syscall.Signal
+	if container.Config.StopSignal != "" {
+		stopSignal, _ = signal.ParseSignal(container.Config.StopSignal)
+	}
+
+	if int(stopSignal) == 0 {
+		stopSignal, _ = signal.ParseSignal(signal.DefaultStopSignal)
+	}
+	return int(stopSignal)
+}

+ 28 - 0
daemon/container_unit_test.go

@@ -31,3 +31,31 @@ func TestValidContainerNames(t *testing.T) {
 		}
 		}
 	}
 	}
 }
 }
+
+func TestContainerStopSignal(t *testing.T) {
+	c := &Container{
+		CommonContainer: CommonContainer{
+			Config: &runconfig.Config{},
+		},
+	}
+
+	def, err := signal.ParseSignal(signal.DefaultStopSignal)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	s := c.stopSignal()
+	if s != int(def) {
+		t.Fatalf("Expected %v, got %v", def, s)
+	}
+
+	c = &Container{
+		CommonContainer: CommonContainer{
+			Config: &runconfig.Config{StopSignal: "SIGKILL"},
+		},
+	}
+	s = c.stopSignal()
+	if s != 9 {
+		t.Fatalf("Expected 9, got %v", s)
+	}
+}

+ 5 - 0
daemon/daemon.go

@@ -1095,6 +1095,11 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig,
 		}
 		}
 	}
 	}
 
 
+	_, err := signal.ParseSignal(config.StopSignal)
+	if err != nil {
+		return nil, err
+	}
+
 	// Now do platform-specific verification
 	// Now do platform-specific verification
 	return verifyPlatformContainerSettings(daemon, hostConfig, config)
 	return verifyPlatformContainerSettings(daemon, hostConfig, config)
 }
 }

+ 1 - 0
docs/reference/api/docker_remote_api.md

@@ -82,6 +82,7 @@ This section lists each version from latest to oldest.  Each listing includes a
 * `DELETE /volumes/(name)`remove a volume with the specified name.
 * `DELETE /volumes/(name)`remove a volume with the specified name.
 * `VolumeDriver` has been moved from config to hostConfig to make the configuration portable.
 * `VolumeDriver` has been moved from config to hostConfig to make the configuration portable.
 * `GET /images/(name)/json` now returns information about tags of the image.
 * `GET /images/(name)/json` now returns information about tags of the image.
+* The `config` option now accepts the field `StopSignal`, which specifies the signal to use to kill a container.
 
 
 
 
 ### v1.20 API changes
 ### v1.20 API changes

+ 16 - 0
integration-cli/docker_cli_run_unix_test.go

@@ -315,3 +315,19 @@ func (s *DockerSuite) TestRunWithSwappinessInvalid(c *check.C) {
 		c.Fatalf("failed. test was able to set invalid value, output: %q", out)
 		c.Fatalf("failed. test was able to set invalid value, output: %q", out)
 	}
 	}
 }
 }
+
+func (s *DockerSuite) TestStopContainerSignal(c *check.C) {
+	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`)
+	containerID := strings.TrimSpace(out)
+
+	if err := waitRun(containerID); err != nil {
+		c.Fatal(err)
+	}
+
+	dockerCmd(c, "stop", containerID)
+	out, _ = dockerCmd(c, "logs", containerID)
+
+	if !strings.Contains(out, "exit trapped") {
+		c.Fatalf("Expected `exit trapped` in the log, got %v", out)
+	}
+}

+ 4 - 0
man/docker-create.1.md

@@ -51,6 +51,7 @@ docker-create - Create a new container
 [**--read-only**[=*false*]]
 [**--read-only**[=*false*]]
 [**--restart**[=*RESTART*]]
 [**--restart**[=*RESTART*]]
 [**--security-opt**[=*[]*]]
 [**--security-opt**[=*[]*]]
+[**--stop-signal**[=*SIGNAL*]]
 [**-t**|**--tty**[=*false*]]
 [**-t**|**--tty**[=*false*]]
 [**-u**|**--user**[=*USER*]]
 [**-u**|**--user**[=*USER*]]
 [**--ulimit**[=*[]*]]
 [**--ulimit**[=*[]*]]
@@ -239,6 +240,9 @@ This value should always larger than **-m**, so you should always use this with
 **--security-opt**=[]
 **--security-opt**=[]
    Security Options
    Security Options
 
 
+**--stop-signal**=SIGTERM
+  Signal to stop a container. Default is SIGTERM.
+
 **-t**, **--tty**=*true*|*false*
 **-t**, **--tty**=*true*|*false*
    Allocate a pseudo-TTY. The default is *false*.
    Allocate a pseudo-TTY. The default is *false*.
 
 

+ 2 - 1
man/docker-inspect.1.md

@@ -180,7 +180,8 @@ To get information on a container use its ID or instance name:
         "Memory": 0,
         "Memory": 0,
         "MemorySwap": 0,
         "MemorySwap": 0,
         "CpuShares": 0,
         "CpuShares": 0,
-        "Cpuset": ""
+        "Cpuset": "",
+        "StopSignal": 15,
     }
     }
     }
     }
     ]
     ]

+ 5 - 1
man/docker-run.1.md

@@ -53,6 +53,7 @@ docker-run - Run a command in a new container
 [**--restart**[=*RESTART*]]
 [**--restart**[=*RESTART*]]
 [**--rm**[=*false*]]
 [**--rm**[=*false*]]
 [**--security-opt**[=*[]*]]
 [**--security-opt**[=*[]*]]
+[**--stop-signal**[=*SIGNAL*]]
 [**--sig-proxy**[=*true*]]
 [**--sig-proxy**[=*true*]]
 [**-t**|**--tty**[=*false*]]
 [**-t**|**--tty**[=*false*]]
 [**-u**|**--user**[=*USER*]]
 [**-u**|**--user**[=*USER*]]
@@ -371,7 +372,7 @@ its root filesystem mounted as read only prohibiting any writes.
 
 
 **--restart**="no"
 **--restart**="no"
    Restart policy to apply when a container exits (no, on-failure[:max-retry], always, unless-stopped).
    Restart policy to apply when a container exits (no, on-failure[:max-retry], always, unless-stopped).
-      
+
 **--rm**=*true*|*false*
 **--rm**=*true*|*false*
    Automatically remove the container when it exits (incompatible with -d). The default is *false*.
    Automatically remove the container when it exits (incompatible with -d). The default is *false*.
 
 
@@ -384,6 +385,9 @@ its root filesystem mounted as read only prohibiting any writes.
     "label:level:LEVEL" : Set the label level for the container
     "label:level:LEVEL" : Set the label level for the container
     "label:disable"     : Turn off label confinement for the container
     "label:disable"     : Turn off label confinement for the container
 
 
+**--stop-signal**=SIGTERM
+  Signal to stop a container. Default is SIGTERM.
+
 **--sig-proxy**=*true*|*false*
 **--sig-proxy**=*true*|*false*
    Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP, and SIGKILL are not proxied. The default is *true*.
    Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP, and SIGKILL are not proxied. The default is *true*.
 
 

+ 1 - 1
man/docker-stop.1.md

@@ -19,7 +19,7 @@ Stop a running container (Send SIGTERM, and then SIGKILL after
   Print usage statement
   Print usage statement
 
 
 **-t**, **--time**=10
 **-t**, **--time**=10
-   Number of seconds to wait for the container to stop before killing it. Default is 10 seconds.
+  Number of seconds to wait for the container to stop before killing it. Default is 10 seconds.
 
 
 #See also
 #See also
 **docker-start(1)** to restart a stopped container.
 **docker-start(1)** to restart a stopped container.

+ 21 - 0
pkg/signal/signal.go

@@ -3,8 +3,12 @@
 package signal
 package signal
 
 
 import (
 import (
+	"fmt"
 	"os"
 	"os"
 	"os/signal"
 	"os/signal"
+	"strconv"
+	"strings"
+	"syscall"
 )
 )
 
 
 // CatchAll catches all signals and relays them to the specified channel.
 // CatchAll catches all signals and relays them to the specified channel.
@@ -21,3 +25,20 @@ func StopCatch(sigc chan os.Signal) {
 	signal.Stop(sigc)
 	signal.Stop(sigc)
 	close(sigc)
 	close(sigc)
 }
 }
+
+// ParseSignal translates a string to a valid syscall signal.
+// It returns an error if the signal map doesn't include the given signal.
+func ParseSignal(rawSignal string) (syscall.Signal, error) {
+	s, err := strconv.Atoi(rawSignal)
+	if err == nil {
+		if s == 0 {
+			return -1, fmt.Errorf("Invalid signal: %s", rawSignal)
+		}
+		return syscall.Signal(s), nil
+	}
+	signal, ok := SignalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")]
+	if !ok {
+		return -1, fmt.Errorf("Invalid signal: %s", rawSignal)
+	}
+	return signal, nil
+}

+ 8 - 5
pkg/signal/signal_unix.go

@@ -9,8 +9,11 @@ import (
 // Signals used in api/client (no windows equivalent, use
 // Signals used in api/client (no windows equivalent, use
 // invalid signals so they don't get handled)
 // invalid signals so they don't get handled)
 
 
-// SIGCHLD is a signal sent to a process when a child process terminates, is interrupted, or resumes after being interrupted.
-const SIGCHLD = syscall.SIGCHLD
-
-// SIGWINCH is a signal sent to a process when its controlling terminal changes its size
-const SIGWINCH = syscall.SIGWINCH
+const (
+	// SIGCHLD is a signal sent to a process when a child process terminates, is interrupted, or resumes after being interrupted.
+	SIGCHLD = syscall.SIGCHLD
+	// SIGWINCH is a signal sent to a process when its controlling terminal changes its size
+	SIGWINCH = syscall.SIGWINCH
+	// DefaultStopSignal is the syscall signal used to stop a container in unix systems.
+	DefaultStopSignal = "SIGTERM"
+)

+ 2 - 0
pkg/signal/signal_windows.go

@@ -11,4 +11,6 @@ import (
 const (
 const (
 	SIGCHLD  = syscall.Signal(0xff)
 	SIGCHLD  = syscall.Signal(0xff)
 	SIGWINCH = syscall.Signal(0xff)
 	SIGWINCH = syscall.Signal(0xff)
+	// DefaultStopSignal is the syscall signal used to stop a container in windows systems.
+	DefaultStopSignal = "15"
 )
 )

+ 1 - 0
runconfig/config.go

@@ -34,6 +34,7 @@ type Config struct {
 	MacAddress      string                // Mac Address of the container
 	MacAddress      string                // Mac Address of the container
 	OnBuild         []string              // ONBUILD metadata that were defined on the image Dockerfile
 	OnBuild         []string              // ONBUILD metadata that were defined on the image Dockerfile
 	Labels          map[string]string     // List of labels set to this container
 	Labels          map[string]string     // List of labels set to this container
+	StopSignal      string                // Signal to stop a container
 }
 }
 
 
 // DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
 // DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper

+ 3 - 0
runconfig/parse.go

@@ -9,6 +9,7 @@ import (
 	flag "github.com/docker/docker/pkg/mflag"
 	flag "github.com/docker/docker/pkg/mflag"
 	"github.com/docker/docker/pkg/nat"
 	"github.com/docker/docker/pkg/nat"
 	"github.com/docker/docker/pkg/parsers"
 	"github.com/docker/docker/pkg/parsers"
+	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/stringutils"
 	"github.com/docker/docker/pkg/stringutils"
 	"github.com/docker/docker/pkg/units"
 	"github.com/docker/docker/pkg/units"
 )
 )
@@ -93,6 +94,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		flLoggingDriver   = cmd.String([]string{"-log-driver"}, "", "Logging driver for container")
 		flLoggingDriver   = cmd.String([]string{"-log-driver"}, "", "Logging driver for container")
 		flCgroupParent    = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
 		flCgroupParent    = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
 		flVolumeDriver    = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
 		flVolumeDriver    = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
+		flStopSignal      = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
 	)
 	)
 
 
 	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
 	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
 		Entrypoint:      entrypoint,
 		Entrypoint:      entrypoint,
 		WorkingDir:      *flWorkingDir,
 		WorkingDir:      *flWorkingDir,
 		Labels:          convertKVStringsToMap(labels),
 		Labels:          convertKVStringsToMap(labels),
+		StopSignal:      *flStopSignal,
 	}
 	}
 
 
 	hostConfig := &HostConfig{
 	hostConfig := &HostConfig{