Merge pull request #15307 from calavera/stop_signal

Signal to stop a container.
This commit is contained in:
David Calavera 2015-09-11 09:25:18 -07:00
commit db54c79d7c
31 changed files with 214 additions and 43 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

@ -17,6 +17,7 @@ const (
Expose = "expose"
Volume = "volume"
User = "user"
StopSignal = "stopsignal"
)
// Commands is list of all Dockerfile commands
@ -35,4 +36,5 @@ var Commands = map[string]struct{}{
Expose: {},
Volume: {},
User: {},
StopSignal: {},
}

View file

@ -20,6 +20,7 @@ import (
"github.com/Sirupsen/logrus"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/nat"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/runconfig"
@ -534,3 +535,21 @@ func volume(b *builder, args []string, attributes map[string]bool, original stri
}
return nil
}
// STOPSIGNAL signal
//
// Set the signal that will be used to kill the container.
func stopSignal(b *builder, args []string, attributes map[string]bool, original string) error {
if len(args) != 1 {
return fmt.Errorf("STOPSIGNAL requires exactly one argument")
}
sig := args[0]
_, err := signal.ParseSignal(sig)
if err != nil {
return err
}
b.Config.StopSignal = sig
return b.commit("", b.Config.Cmd, fmt.Sprintf("STOPSIGNAL %v", args))
}

View file

@ -45,14 +45,15 @@ import (
// Environment variable interpolation will happen on these statements only.
var replaceEnvAllowed = map[string]struct{}{
command.Env: {},
command.Label: {},
command.Add: {},
command.Copy: {},
command.Workdir: {},
command.Expose: {},
command.Volume: {},
command.User: {},
command.Env: {},
command.Label: {},
command.Add: {},
command.Copy: {},
command.Workdir: {},
command.Expose: {},
command.Volume: {},
command.User: {},
command.StopSignal: {},
}
var evaluateTable map[string]func(*builder, []string, map[string]bool, string) error
@ -73,6 +74,7 @@ func init() {
command.Expose: expose,
command.Volume: volume,
command.User: user,
command.StopSignal: stopSignal,
}
}

View file

@ -61,6 +61,7 @@ func init() {
command.Entrypoint: parseMaybeJSON,
command.Expose: parseStringsWhitespaceDelimited,
command.Volume: parseMaybeJSONToList,
command.StopSignal: parseString,
}
}

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

@ -23,6 +23,7 @@
<item> WORKDIR </item>
<item> USER </item>
<item> LABEL </item>
<item> STOPSIGNAL </item>
</list>
<contexts>

View file

@ -25,7 +25,7 @@
</dict>
</dict>
<key>match</key>
<string>^\s*(?:(ONBUILD)\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR|COPY|LABEL)\s</string>
<string>^\s*(?:(ONBUILD)\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR|COPY|LABEL|STOPSIGNAL)\s</string>
</dict>
<dict>
<key>captures</key>

View file

@ -11,7 +11,7 @@ let b:current_syntax = "dockerfile"
syntax case ignore
syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|LABEL|VOLUME|WORKDIR|COPY)\s/
syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|LABEL|VOLUME|WORKDIR|COPY|STOPSIGNAL)\s/
highlight link dockerfileKeyword Keyword
syntax region dockerfileString start=/\v"/ skip=/\v\\./ end=/\v"/

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

@ -1,6 +1,11 @@
package daemon
import "testing"
import (
"testing"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/runconfig"
)
func TestGetFullName(t *testing.T) {
name, err := GetFullContainerName("testing")
@ -31,3 +36,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

@ -1076,6 +1076,13 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *runconfig.HostConfig,
return nil, fmt.Errorf("The working directory '%s' is invalid. It needs to be an absolute path.", config.WorkingDir)
}
}
if len(config.StopSignal) > 0 {
_, err := signal.ParseSignal(config.StopSignal)
if err != nil {
return nil, err
}
}
}
if hostConfig == nil {

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

@ -166,6 +166,7 @@ Create a container
"ExposedPorts": {
"22/tcp": {}
},
"StopSignal": "SIGTERM",
"HostConfig": {
"Binds": ["/tmp:/tmp"],
"Links": ["redis3:redis"],
@ -250,6 +251,7 @@ Json Parameters:
container
- **ExposedPorts** - An object mapping ports to an empty object in the form of:
`"ExposedPorts": { "<port>/<tcp|udp>: {}" }`
- **StopSignal** - Signal to stop a container as a string or unsigned integer. `SIGTERM` by default.
- **HostConfig**
- **Binds** A list of volume bindings for this container. Each volume binding is a string in one of these forms:
+ `container_path` to create a new volume for the container
@ -367,7 +369,8 @@ Return low-level information on the container `id`
"Tty": false,
"User": "",
"Volumes": null,
"WorkingDir": ""
"WorkingDir": "",
"StopSignal": "SIGTERM"
},
"Created": "2015-01-06T15:47:31.485331387Z",
"Driver": "devicemapper",

View file

@ -158,6 +158,7 @@ the `Dockerfile`:
* `USER`
* `WORKDIR`
* `VOLUME`
* `STOPSIGNAL`
as well as:
@ -1012,6 +1013,14 @@ For example you might add something like this:
> **Warning**: The `ONBUILD` instruction may not trigger `FROM` or `MAINTAINER` instructions.
## STOPSIGNAL
STOPSIGNAL signal
The `STOPSIGNAL` instruction sets the system call signal that will be sent to the container to exit.
This signal can be a valid unsigned number that matches a position in the kernel's syscall table, for instance 9,
or a signal name in the format SIGNAME, for instance SIGKILL.
## Dockerfile examples
# Nginx

View file

@ -61,6 +61,7 @@ Creates a new container.
--read-only=false Mount the container's root filesystem as read only
--restart="no" Restart policy (no, on-failure[:max-retry], always, unless-stopped)
--security-opt=[] Security options
--stop-signal="SIGTERM" Signal to stop a container
-t, --tty=false Allocate a pseudo-TTY
--disable-content-trust=true Skip image verification
-u, --user="" Username or UID

View file

@ -62,6 +62,7 @@ weight=1
--restart="no" Restart policy (no, on-failure[:max-retry], always, unless-stopped)
--rm=false Automatically remove the container when it exits
--security-opt=[] Security Options
--stop-signal="SIGTERM" Signal to stop a container
--sig-proxy=true Proxy received signals to the process
-t, --tty=false Allocate a pseudo-TTY
-u, --user="" Username or UID (format: <name|uid>[:<group|gid>])
@ -531,3 +532,9 @@ containers with `daemon` user:
The 4th container fails and reports "[8] System error: resource temporarily unavailable" error.
This fails because the caller set `nproc=3` resulting in the first three containers using up
the three processes quota set for the `daemon` user.
### Stopping a container with a specific signal
The `--stop-signal` flag sets the system call signal that will be sent to the container to exit.
This signal can be a valid unsigned number that matches a position in the kernel's syscall table, for instance 9,
or a signal name in the format SIGNAME, for instance SIGKILL.

View file

@ -5660,3 +5660,18 @@ func (s *DockerSuite) TestBuildNullStringInAddCopyVolume(c *check.C) {
_, err = buildImageFromContext(name, ctx, true)
c.Assert(err, check.IsNil)
}
func (s *DockerSuite) TestBuildStopSignal(c *check.C) {
name := "test_build_stop_signal"
_, err := buildImage(name,
`FROM busybox
STOPSIGNAL SIGKILL`,
true)
c.Assert(err, check.IsNil)
res, err := inspectFieldJSON(name, "Config.StopSignal")
c.Assert(err, check.IsNil)
if res != `"SIGKILL"` {
c.Fatalf("Signal %s, expected SIGKILL", res)
}
}

View file

@ -458,3 +458,15 @@ func (s *DockerTrustSuite) TestTrustedCreateFromBadTrustServer(c *check.C) {
c.Fatalf("Missing expected output on trusted push:\n%s", out)
}
}
func (s *DockerSuite) TestCreateStopSignal(c *check.C) {
name := "test_create_stop_signal"
dockerCmd(c, "create", "--name", name, "--stop-signal", "9", "busybox")
res, err := inspectFieldJSON(name, "Config.StopSignal")
c.Assert(err, check.IsNil)
if res != `"9"` {
c.Fatalf("Expected 9, got %s", res)
}
}

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": "SIGTERM"
}
}
]

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{