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

Update docker stop and docker restart to allow not specifying timeout and use the one specified at container creation time.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
This commit is contained in:
Yong Tang 2016-06-06 20:29:05 -07:00
parent e66d210891
commit cc703784f3
13 changed files with 103 additions and 34 deletions

View file

@ -37,10 +37,10 @@ type stateBackend interface {
ContainerPause(name string) error ContainerPause(name string) error
ContainerRename(oldName, newName string) error ContainerRename(oldName, newName string) error
ContainerResize(name string, height, width int) error ContainerResize(name string, height, width int) error
ContainerRestart(name string, seconds int) error ContainerRestart(name string, seconds *int) error
ContainerRm(name string, config *types.ContainerRmConfig) error ContainerRm(name string, config *types.ContainerRmConfig) error
ContainerStart(name string, hostConfig *container.HostConfig, validateHostname bool, checkpoint string) error ContainerStart(name string, hostConfig *container.HostConfig, validateHostname bool, checkpoint string) error
ContainerStop(name string, seconds int) error ContainerStop(name string, seconds *int) error
ContainerUnpause(name string) error ContainerUnpause(name string) error
ContainerUpdate(name string, hostConfig *container.HostConfig, validateHostname bool) (types.ContainerUpdateResponse, error) ContainerUpdate(name string, hostConfig *container.HostConfig, validateHostname bool) (types.ContainerUpdateResponse, error)
ContainerWait(name string, timeout time.Duration) (int, error) ContainerWait(name string, timeout time.Duration) (int, error)

View file

@ -169,7 +169,14 @@ func (s *containerRouter) postContainersStop(ctx context.Context, w http.Respons
return err return err
} }
seconds, _ := strconv.Atoi(r.Form.Get("t")) var seconds *int
if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
valSeconds, err := strconv.Atoi(tmpSeconds)
if err != nil {
return err
}
seconds = &valSeconds
}
if err := s.backend.ContainerStop(vars["name"], seconds); err != nil { if err := s.backend.ContainerStop(vars["name"], seconds); err != nil {
return err return err
@ -223,9 +230,16 @@ func (s *containerRouter) postContainersRestart(ctx context.Context, w http.Resp
return err return err
} }
timeout, _ := strconv.Atoi(r.Form.Get("t")) var seconds *int
if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
valSeconds, err := strconv.Atoi(tmpSeconds)
if err != nil {
return err
}
seconds = &valSeconds
}
if err := s.backend.ContainerRestart(vars["name"], timeout); err != nil { if err := s.backend.ContainerRestart(vars["name"], seconds); err != nil {
return err return err
} }

View file

@ -13,7 +13,8 @@ import (
) )
type restartOptions struct { type restartOptions struct {
nSeconds int nSeconds int
nSecondsChanged bool
containers []string containers []string
} }
@ -28,6 +29,7 @@ func NewRestartCommand(dockerCli *command.DockerCli) *cobra.Command {
Args: cli.RequiresMinArgs(1), Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
opts.containers = args opts.containers = args
opts.nSecondsChanged = cmd.Flags().Changed("time")
return runRestart(dockerCli, &opts) return runRestart(dockerCli, &opts)
}, },
} }
@ -40,9 +42,14 @@ func NewRestartCommand(dockerCli *command.DockerCli) *cobra.Command {
func runRestart(dockerCli *command.DockerCli, opts *restartOptions) error { func runRestart(dockerCli *command.DockerCli, opts *restartOptions) error {
ctx := context.Background() ctx := context.Background()
var errs []string var errs []string
var timeout *time.Duration
if opts.nSecondsChanged {
timeoutValue := time.Duration(opts.nSeconds) * time.Second
timeout = &timeoutValue
}
for _, name := range opts.containers { for _, name := range opts.containers {
timeout := time.Duration(opts.nSeconds) * time.Second if err := dockerCli.Client().ContainerRestart(ctx, name, timeout); err != nil {
if err := dockerCli.Client().ContainerRestart(ctx, name, &timeout); err != nil {
errs = append(errs, err.Error()) errs = append(errs, err.Error())
} else { } else {
fmt.Fprintf(dockerCli.Out(), "%s\n", name) fmt.Fprintf(dockerCli.Out(), "%s\n", name)

View file

@ -13,7 +13,8 @@ import (
) )
type stopOptions struct { type stopOptions struct {
time int time int
timeChanged bool
containers []string containers []string
} }
@ -28,6 +29,7 @@ func NewStopCommand(dockerCli *command.DockerCli) *cobra.Command {
Args: cli.RequiresMinArgs(1), Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
opts.containers = args opts.containers = args
opts.timeChanged = cmd.Flags().Changed("time")
return runStop(dockerCli, &opts) return runStop(dockerCli, &opts)
}, },
} }
@ -39,12 +41,17 @@ func NewStopCommand(dockerCli *command.DockerCli) *cobra.Command {
func runStop(dockerCli *command.DockerCli, opts *stopOptions) error { func runStop(dockerCli *command.DockerCli, opts *stopOptions) error {
ctx := context.Background() ctx := context.Background()
timeout := time.Duration(opts.time) * time.Second
var timeout *time.Duration
if opts.timeChanged {
timeoutValue := time.Duration(opts.time) * time.Second
timeout = &timeoutValue
}
var errs []string var errs []string
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, id string) error { errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, id string) error {
return dockerCli.Client().ContainerStop(ctx, id, &timeout) return dockerCli.Client().ContainerStop(ctx, id, timeout)
}) })
for _, container := range opts.containers { for _, container := range opts.containers {
if err := <-errChan; err != nil { if err := <-errChan; err != nil {

View file

@ -296,7 +296,7 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) {
// Wait for serve API to complete // Wait for serve API to complete
errAPI := <-serveAPIWait errAPI := <-serveAPIWait
c.Cleanup() c.Cleanup()
shutdownDaemon(d, 15) shutdownDaemon(d)
containerdRemote.Cleanup() containerdRemote.Cleanup()
if errAPI != nil { if errAPI != nil {
return fmt.Errorf("Shutting down due to ServeAPI error: %v", errAPI) return fmt.Errorf("Shutting down due to ServeAPI error: %v", errAPI)
@ -342,16 +342,22 @@ func (cli *DaemonCli) stop() {
// shutdownDaemon just wraps daemon.Shutdown() to handle a timeout in case // shutdownDaemon just wraps daemon.Shutdown() to handle a timeout in case
// d.Shutdown() is waiting too long to kill container or worst it's // d.Shutdown() is waiting too long to kill container or worst it's
// blocked there // blocked there
func shutdownDaemon(d *daemon.Daemon, timeout time.Duration) { func shutdownDaemon(d *daemon.Daemon) {
shutdownTimeout := d.ShutdownTimeout()
ch := make(chan struct{}) ch := make(chan struct{})
go func() { go func() {
d.Shutdown() d.Shutdown()
close(ch) close(ch)
}() }()
if shutdownTimeout < 0 {
<-ch
logrus.Debug("Clean shutdown succeeded")
return
}
select { select {
case <-ch: case <-ch:
logrus.Debug("Clean shutdown succeeded") logrus.Debug("Clean shutdown succeeded")
case <-time.After(timeout * time.Second): case <-time.After(time.Duration(shutdownTimeout) * time.Second):
logrus.Error("Force shutdown daemon") logrus.Error("Force shutdown daemon")
} }
} }

View file

@ -45,8 +45,8 @@ import (
const configFileName = "config.v2.json" const configFileName = "config.v2.json"
const ( const (
// defaultStopTimeout is the timeout (in seconds) for the syscall signal used to stop a container. // DefaultStopTimeout is the timeout (in seconds) for the syscall signal used to stop a container.
defaultStopTimeout = 10 DefaultStopTimeout = 10
) )
var ( var (
@ -588,7 +588,7 @@ func (container *Container) StopTimeout() int {
if container.Config.StopTimeout != nil { if container.Config.StopTimeout != nil {
return *container.Config.StopTimeout return *container.Config.StopTimeout
} }
return defaultStopTimeout return DefaultStopTimeout
} }
// InitDNSHostConfig ensures that the dns fields are never nil. // InitDNSHostConfig ensures that the dns fields are never nil.

View file

@ -43,8 +43,8 @@ func TestContainerStopTimeout(t *testing.T) {
} }
s := c.StopTimeout() s := c.StopTimeout()
if s != defaultStopTimeout { if s != DefaultStopTimeout {
t.Fatalf("Expected %v, got %v", defaultStopTimeout, s) t.Fatalf("Expected %v, got %v", DefaultStopTimeout, s)
} }
stopTimeout := 15 stopTimeout := 15

View file

@ -25,7 +25,7 @@ type Backend interface {
PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error
CreateManagedContainer(config types.ContainerCreateConfig, validateHostname bool) (types.ContainerCreateResponse, error) CreateManagedContainer(config types.ContainerCreateConfig, validateHostname bool) (types.ContainerCreateResponse, error)
ContainerStart(name string, hostConfig *container.HostConfig, validateHostname bool, checkpoint string) error ContainerStart(name string, hostConfig *container.HostConfig, validateHostname bool, checkpoint string) error
ContainerStop(name string, seconds int) error ContainerStop(name string, seconds *int) error
ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
UpdateContainerServiceConfig(containerName string, serviceConfig *clustertypes.ServiceConfig) error UpdateContainerServiceConfig(containerName string, serviceConfig *clustertypes.ServiceConfig) error
ContainerInspectCurrent(name string, size bool) (*types.ContainerJSON, error) ContainerInspectCurrent(name string, size bool) (*types.ContainerJSON, error)

View file

@ -279,11 +279,12 @@ func (c *containerAdapter) wait(ctx context.Context) error {
} }
func (c *containerAdapter) shutdown(ctx context.Context) error { func (c *containerAdapter) shutdown(ctx context.Context) error {
// Default stop grace period to 10s. // Default stop grace period to nil (daemon will use the stopTimeout of the container)
stopgrace := 10 var stopgrace *int
spec := c.container.spec() spec := c.container.spec()
if spec.StopGracePeriod != nil { if spec.StopGracePeriod != nil {
stopgrace = int(spec.StopGracePeriod.Seconds) stopgraceValue := int(spec.StopGracePeriod.Seconds)
stopgrace = &stopgraceValue
} }
return c.backend.ContainerStop(c.container.name(), stopgrace) return c.backend.ContainerStop(c.container.name(), stopgrace)
} }

View file

@ -692,6 +692,8 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot
} }
func (daemon *Daemon) shutdownContainer(c *container.Container) error { func (daemon *Daemon) shutdownContainer(c *container.Container) error {
stopTimeout := c.StopTimeout()
// TODO(windows): Handle docker restart with paused containers // TODO(windows): Handle docker restart with paused containers
if c.IsPaused() { if c.IsPaused() {
// To terminate a process in freezer cgroup, we should send // To terminate a process in freezer cgroup, we should send
@ -708,8 +710,8 @@ func (daemon *Daemon) shutdownContainer(c *container.Container) error {
if err := daemon.containerUnpause(c); err != nil { if err := daemon.containerUnpause(c); err != nil {
return fmt.Errorf("Failed to unpause container %s with error: %v", c.ID, err) return fmt.Errorf("Failed to unpause container %s with error: %v", c.ID, err)
} }
if _, err := c.WaitStop(time.Duration(c.StopTimeout()) * time.Second); err != nil { if _, err := c.WaitStop(time.Duration(stopTimeout) * time.Second); err != nil {
logrus.Debugf("container %s failed to exit in %d second of SIGTERM, sending SIGKILL to force", c.ID, c.StopTimeout()) logrus.Debugf("container %s failed to exit in %d second of SIGTERM, sending SIGKILL to force", c.ID, stopTimeout)
sig, ok := signal.SignalMap["KILL"] sig, ok := signal.SignalMap["KILL"]
if !ok { if !ok {
return fmt.Errorf("System does not support SIGKILL") return fmt.Errorf("System does not support SIGKILL")
@ -721,8 +723,8 @@ func (daemon *Daemon) shutdownContainer(c *container.Container) error {
return err return err
} }
} }
// If container failed to exit in c.StopTimeout() seconds of SIGTERM, then using the force // If container failed to exit in stopTimeout seconds of SIGTERM, then using the force
if err := daemon.containerStop(c, c.StopTimeout()); err != nil { if err := daemon.containerStop(c, stopTimeout); err != nil {
return fmt.Errorf("Failed to stop container %s with error: %v", c.ID, err) return fmt.Errorf("Failed to stop container %s with error: %v", c.ID, err)
} }
@ -730,6 +732,29 @@ func (daemon *Daemon) shutdownContainer(c *container.Container) error {
return nil return nil
} }
// ShutdownTimeout returns the shutdown timeout based on the max stopTimeout of the containers
func (daemon *Daemon) ShutdownTimeout() int {
// By default we use container.DefaultStopTimeout + 5s, which is 15s.
// TODO (yongtang): Will need to allow shutdown-timeout once #23036 is in place.
graceTimeout := 5
shutdownTimeout := container.DefaultStopTimeout + graceTimeout
if daemon.containers != nil {
for _, c := range daemon.containers.List() {
if shutdownTimeout >= 0 {
stopTimeout := c.StopTimeout()
if stopTimeout < 0 {
shutdownTimeout = -1
} else {
if stopTimeout+graceTimeout > shutdownTimeout {
shutdownTimeout = stopTimeout + graceTimeout
}
}
}
}
}
return shutdownTimeout
}
// Shutdown stops the daemon. // Shutdown stops the daemon.
func (daemon *Daemon) Shutdown() error { func (daemon *Daemon) Shutdown() error {
daemon.shutdown = true daemon.shutdown = true

View file

@ -13,15 +13,20 @@ import (
// timeout, ContainerRestart will wait forever until a graceful // timeout, ContainerRestart will wait forever until a graceful
// stop. Returns an error if the container cannot be found, or if // stop. Returns an error if the container cannot be found, or if
// there is an underlying error at any stage of the restart. // there is an underlying error at any stage of the restart.
func (daemon *Daemon) ContainerRestart(name string, seconds int) error { func (daemon *Daemon) ContainerRestart(name string, seconds *int) error {
container, err := daemon.GetContainer(name) container, err := daemon.GetContainer(name)
if err != nil { if err != nil {
return err return err
} }
if err := daemon.containerRestart(container, seconds); err != nil { if seconds == nil {
stopTimeout := container.StopTimeout()
seconds = &stopTimeout
}
if err := daemon.containerRestart(container, *seconds); err != nil {
return fmt.Errorf("Cannot restart container %s: %v", name, err) return fmt.Errorf("Cannot restart container %s: %v", name, err)
} }
return nil return nil
} }
// containerRestart attempts to gracefully stop and then start the // containerRestart attempts to gracefully stop and then start the

View file

@ -16,7 +16,7 @@ import (
// will wait for a graceful termination. An error is returned if the // will wait for a graceful termination. An error is returned if the
// container is not found, is already stopped, or if there is a // container is not found, is already stopped, or if there is a
// problem stopping the container. // problem stopping the container.
func (daemon *Daemon) ContainerStop(name string, seconds int) error { func (daemon *Daemon) ContainerStop(name string, seconds *int) error {
container, err := daemon.GetContainer(name) container, err := daemon.GetContainer(name)
if err != nil { if err != nil {
return err return err
@ -25,7 +25,11 @@ func (daemon *Daemon) ContainerStop(name string, seconds int) error {
err := fmt.Errorf("Container %s is already stopped", name) err := fmt.Errorf("Container %s is already stopped", name)
return errors.NewErrorWithStatusCode(err, http.StatusNotModified) return errors.NewErrorWithStatusCode(err, http.StatusNotModified)
} }
if err := daemon.containerStop(container, seconds); err != nil { if seconds == nil {
stopTimeout := container.StopTimeout()
seconds = &stopTimeout
}
if err := daemon.containerStop(container, *seconds); err != nil {
return fmt.Errorf("Cannot stop container %s: %v", name, err) return fmt.Errorf("Cannot stop container %s: %v", name, err)
} }
return nil return nil

View file

@ -94,6 +94,7 @@ type ContainerOptions struct {
cgroupParent string cgroupParent string
volumeDriver string volumeDriver string
stopSignal string stopSignal string
stopTimeout int
isolation string isolation string
shmSize string shmSize string
noHealthcheck bool noHealthcheck bool
@ -106,7 +107,6 @@ type ContainerOptions struct {
init bool init bool
initPath string initPath string
credentialSpec string credentialSpec string
stopTimeout int
Image string Image string
Args []string Args []string
@ -146,7 +146,6 @@ func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
ulimits: NewUlimitOpt(nil), ulimits: NewUlimitOpt(nil),
volumes: opts.NewListOpts(nil), volumes: opts.NewListOpts(nil),
volumesFrom: opts.NewListOpts(nil), volumesFrom: opts.NewListOpts(nil),
stopSignal: flags.String("stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal)),
} }
// General purpose flags // General purpose flags
@ -163,6 +162,7 @@ func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only") flags.BoolVar(&copts.readonlyRootfs, "read-only", false, "Mount the container's root filesystem as read only")
flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits") flags.StringVar(&copts.restartPolicy, "restart", "no", "Restart policy to apply when a container exits")
flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal)) flags.StringVar(&copts.stopSignal, "stop-signal", signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
flags.IntVar(&copts.stopTimeout, "stop-timeout", 0, "Timeout (in seconds) to stop a container")
flags.Var(copts.sysctls, "sysctl", "Sysctl options") flags.Var(copts.sysctls, "sysctl", "Sysctl options")
flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY") flags.BoolVarP(&copts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
flags.Var(copts.ulimits, "ulimit", "Ulimit options") flags.Var(copts.ulimits, "ulimit", "Ulimit options")
@ -561,7 +561,7 @@ func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *c
config.StopSignal = copts.stopSignal config.StopSignal = copts.stopSignal
} }
if flags.Changed("stop-timeout") { if flags.Changed("stop-timeout") {
config.StopTimeout = copts.flStopTimeout config.StopTimeout = &copts.stopTimeout
} }
hostConfig := &container.HostConfig{ hostConfig := &container.HostConfig{