1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

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>
This commit is contained in:
David Calavera 2015-08-04 13:51:48 -07:00
parent bb1996ab8f
commit 0e50d946a2
18 changed files with 124 additions and 32 deletions

View file

@ -6,6 +6,7 @@ import (
"net/http"
"strconv"
"strings"
"syscall"
"time"
"golang.org/x/net/websocket"
@ -220,32 +221,18 @@ func (s *Server) postContainersKill(ctx context.Context, w http.ResponseWriter,
return err
}
var sig uint64
var sig syscall.Signal
name := vars["name"]
// If we have a signal, look at it. Otherwise, do nothing
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)
// 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

View file

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

View file

@ -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 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 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 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)'

View file

@ -502,6 +502,7 @@ __docker_subcommand() {
"($help -d --detach)"{-d,--detach}"[Detached mode: leave the container running in the background]" \
"($help)--rm[Remove intermediate containers when it exits]" \
"($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 -):command: _command_names -e" \
"($help -)*::arguments: _normal" && ret=0

View file

@ -27,6 +27,7 @@ import (
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/nat"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/volume"
@ -495,10 +496,10 @@ func (container *Container) Kill() error {
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
// 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.
func (container *Container) Stop(seconds int) error {
if !container.IsRunning() {
@ -506,9 +507,9 @@ func (container *Container) Stop(seconds int) error {
}
// 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")
if err := container.killPossiblyDeadProcess(int(syscall.SIGKILL)); err != nil {
if err := container.killPossiblyDeadProcess(9); err != nil {
return err
}
}
@ -523,7 +524,7 @@ func (container *Container) Stop(seconds int) error {
}
}
container.logEvent("stop")
container.LogEvent("stop")
return nil
}
@ -1140,3 +1141,15 @@ func (container *Container) copyImagePathContent(v volume.Volume, destination st
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)
}

View file

@ -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)
}
}

View file

@ -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
return verifyPlatformContainerSettings(daemon, hostConfig, config)
}

View file

@ -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.
* `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.
* The `config` option now accepts the field `StopSignal`, which specifies the signal to use to kill a container.
### v1.20 API changes

View file

@ -315,3 +315,19 @@ func (s *DockerSuite) TestRunWithSwappinessInvalid(c *check.C) {
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)
}
}

View file

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

View file

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

View file

@ -53,6 +53,7 @@ docker-run - Run a command in a new container
[**--restart**[=*RESTART*]]
[**--rm**[=*false*]]
[**--security-opt**[=*[]*]]
[**--stop-signal**[=*SIGNAL*]]
[**--sig-proxy**[=*true*]]
[**-t**|**--tty**[=*false*]]
[**-u**|**--user**[=*USER*]]
@ -371,7 +372,7 @@ its root filesystem mounted as read only prohibiting any writes.
**--restart**="no"
Restart policy to apply when a container exits (no, on-failure[:max-retry], always, unless-stopped).
**--rm**=*true*|*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:disable" : Turn off label confinement for the container
**--stop-signal**=SIGTERM
Signal to stop a container. Default is SIGTERM.
**--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*.

View file

@ -19,7 +19,7 @@ Stop a running container (Send SIGTERM, and then SIGKILL after
Print usage statement
**-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
**docker-start(1)** to restart a stopped container.

View file

@ -3,8 +3,12 @@
package signal
import (
"fmt"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
)
// CatchAll catches all signals and relays them to the specified channel.
@ -21,3 +25,20 @@ func StopCatch(sigc chan os.Signal) {
signal.Stop(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
}

View file

@ -9,8 +9,11 @@ import (
// Signals used in api/client (no windows equivalent, use
// 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"
)

View file

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

View file

@ -34,6 +34,7 @@ type Config struct {
MacAddress string // Mac Address of the container
OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile
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

View file

@ -9,6 +9,7 @@ import (
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/nat"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/stringutils"
"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")
flCgroupParent = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup 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")
@ -322,6 +324,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
Entrypoint: entrypoint,
WorkingDir: *flWorkingDir,
Labels: convertKVStringsToMap(labels),
StopSignal: *flStopSignal,
}
hostConfig := &HostConfig{