diff --git a/api/server/router/container/backend.go b/api/server/router/container/backend.go index 75ea1d82b7..564ccd8c99 100644 --- a/api/server/router/container/backend.go +++ b/api/server/router/container/backend.go @@ -37,10 +37,10 @@ type stateBackend interface { ContainerPause(name string) error ContainerRename(oldName, newName string) error ContainerResize(name string, height, width int) error - ContainerRestart(name string, seconds *int) error + ContainerRestart(ctx context.Context, name string, options container.StopOptions) error ContainerRm(name string, config *types.ContainerRmConfig) error ContainerStart(name string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error - ContainerStop(name string, seconds *int) error + ContainerStop(ctx context.Context, name string, options container.StopOptions) error ContainerUnpause(name string) error ContainerUpdate(name string, hostConfig *container.HostConfig) (container.ContainerUpdateOKBody, error) ContainerWait(ctx context.Context, name string, condition containerpkg.WaitCondition) (<-chan containerpkg.StateStatus, error) diff --git a/api/server/router/container/container_routes.go b/api/server/router/container/container_routes.go index 392efc023e..ebb2f7b4d5 100644 --- a/api/server/router/container/container_routes.go +++ b/api/server/router/container/container_routes.go @@ -221,16 +221,16 @@ func (s *containerRouter) postContainersStop(ctx context.Context, w http.Respons return err } - var seconds *int + var options container.StopOptions if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" { valSeconds, err := strconv.Atoi(tmpSeconds) if err != nil { return err } - seconds = &valSeconds + options.Timeout = &valSeconds } - if err := s.backend.ContainerStop(vars["name"], seconds); err != nil { + if err := s.backend.ContainerStop(ctx, vars["name"], options); err != nil { return err } w.WriteHeader(http.StatusNoContent) @@ -278,16 +278,16 @@ func (s *containerRouter) postContainersRestart(ctx context.Context, w http.Resp return err } - var seconds *int + var options container.StopOptions if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" { valSeconds, err := strconv.Atoi(tmpSeconds) if err != nil { return err } - seconds = &valSeconds + options.Timeout = &valSeconds } - if err := s.backend.ContainerRestart(vars["name"], seconds); err != nil { + if err := s.backend.ContainerRestart(ctx, vars["name"], options); err != nil { return err } diff --git a/api/types/container/config.go b/api/types/container/config.go index f767195b94..ee6eb5c7ce 100644 --- a/api/types/container/config.go +++ b/api/types/container/config.go @@ -13,6 +13,19 @@ import ( // Docker interprets it as 3 nanoseconds. const MinimumDuration = 1 * time.Millisecond +// StopOptions holds the options to stop or restart a container. +type StopOptions struct { + // Timeout (optional) is the timeout (in seconds) to wait for the container + // to stop gracefully before forcibly terminating it with SIGKILL. + // + // - Use nil to use the default timeout (10 seconds). + // - Use '-1' to wait indefinitely. + // - Use '0' to not wait for the container to exit gracefully, and + // immediately proceeds to forcibly terminating the container. + // - Other positive values are used as timeout (in seconds). + Timeout *int `json:",omitempty"` +} + // HealthConfig holds configuration settings for the HEALTHCHECK feature. type HealthConfig struct { // Test is the test to perform to check that the container is healthy. diff --git a/daemon/cluster/executor/backend.go b/daemon/cluster/executor/backend.go index f342d522fc..b07c7bd53f 100644 --- a/daemon/cluster/executor/backend.go +++ b/daemon/cluster/executor/backend.go @@ -35,8 +35,8 @@ type Backend interface { ReleaseIngress() (<-chan struct{}, error) CreateManagedContainer(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) ContainerStart(name string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error - ContainerStop(name string, seconds *int) error - ContainerLogs(context.Context, string, *types.ContainerLogsOptions) (msgs <-chan *backend.LogMessage, tty bool, err error) + ContainerStop(ctx context.Context, name string, config container.StopOptions) error + ContainerLogs(ctx context.Context, name string, config *types.ContainerLogsOptions) (msgs <-chan *backend.LogMessage, tty bool, err error) ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error ActivateContainerServiceBinding(containerName string) error DeactivateContainerServiceBinding(containerName string) error diff --git a/daemon/cluster/executor/container/adapter.go b/daemon/cluster/executor/container/adapter.go index 54ee6348fb..b8a7ece9a2 100644 --- a/daemon/cluster/executor/container/adapter.go +++ b/daemon/cluster/executor/container/adapter.go @@ -407,14 +407,13 @@ func (c *containerAdapter) wait(ctx context.Context) (<-chan containerpkg.StateS } func (c *containerAdapter) shutdown(ctx context.Context) error { + var options = containertypes.StopOptions{} // Default stop grace period to nil (daemon will use the stopTimeout of the container) - var stopgrace *int - spec := c.container.spec() - if spec.StopGracePeriod != nil { - stopgraceValue := int(spec.StopGracePeriod.Seconds) - stopgrace = &stopgraceValue + if spec := c.container.spec(); spec.StopGracePeriod != nil { + timeout := int(spec.StopGracePeriod.Seconds) + options.Timeout = &timeout } - return c.backend.ContainerStop(c.container.name(), stopgrace) + return c.backend.ContainerStop(ctx, c.container.name(), options) } func (c *containerAdapter) terminate(ctx context.Context) error { diff --git a/daemon/daemon.go b/daemon/daemon.go index ebb0a51e8f..b7e1eea1c0 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1117,7 +1117,7 @@ func (daemon *Daemon) waitForStartupDone() { func (daemon *Daemon) shutdownContainer(c *container.Container) error { // If container failed to exit in stopTimeout seconds of SIGTERM, then using the force - if err := daemon.containerStop(c, nil); err != nil { + if err := daemon.containerStop(context.TODO(), c, containertypes.StopOptions{}); err != nil { return fmt.Errorf("Failed to stop container %s with error: %v", c.ID, err) } diff --git a/daemon/delete.go b/daemon/delete.go index 05536a67d2..c2bbbd4196 100644 --- a/daemon/delete.go +++ b/daemon/delete.go @@ -1,6 +1,7 @@ package daemon // import "github.com/docker/docker/daemon" import ( + "context" "fmt" "os" "path" @@ -8,6 +9,7 @@ import ( "time" "github.com/docker/docker/api/types" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/errdefs" "github.com/docker/docker/pkg/containerfs" @@ -109,7 +111,7 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, config ty // If you arrived here and know the answer, you earned yourself a picture // of a cute animal of your own choosing. var stopTimeout = 3 - if err := daemon.containerStop(container, &stopTimeout); err != nil { + if err := daemon.containerStop(context.TODO(), container, containertypes.StopOptions{Timeout: &stopTimeout}); err != nil { return err } diff --git a/daemon/restart.go b/daemon/restart.go index 5ac271c5e0..c20e8cbb10 100644 --- a/daemon/restart.go +++ b/daemon/restart.go @@ -1,6 +1,7 @@ package daemon // import "github.com/docker/docker/daemon" import ( + "context" "fmt" containertypes "github.com/docker/docker/api/types/container" @@ -14,12 +15,12 @@ import ( // timeout, ContainerRestart will wait forever until a graceful // stop. Returns an error if the container cannot be found, or if // there is an underlying error at any stage of the restart. -func (daemon *Daemon) ContainerRestart(name string, seconds *int) error { +func (daemon *Daemon) ContainerRestart(ctx context.Context, name string, options containertypes.StopOptions) error { ctr, err := daemon.GetContainer(name) if err != nil { return err } - err = daemon.containerRestart(ctr, seconds) + err = daemon.containerRestart(ctx, ctr, options) if err != nil { return fmt.Errorf("Cannot restart container %s: %v", name, err) } @@ -31,7 +32,7 @@ func (daemon *Daemon) ContainerRestart(name string, seconds *int) error { // container. When stopping, wait for the given duration in seconds to // gracefully stop, before forcefully terminating the container. If // given a negative duration, wait forever for a graceful stop. -func (daemon *Daemon) containerRestart(container *container.Container, seconds *int) error { +func (daemon *Daemon) containerRestart(ctx context.Context, container *container.Container, options containertypes.StopOptions) error { // Determine isolation. If not specified in the hostconfig, use daemon default. actualIsolation := container.HostConfig.Isolation if containertypes.Isolation.IsDefault(actualIsolation) { @@ -56,7 +57,7 @@ func (daemon *Daemon) containerRestart(container *container.Container, seconds * autoRemove := container.HostConfig.AutoRemove container.HostConfig.AutoRemove = false - err := daemon.containerStop(container, seconds) + err := daemon.containerStop(ctx, container, options) // restore AutoRemove irrespective of whether the stop worked or not container.HostConfig.AutoRemove = autoRemove // containerStop will write HostConfig to disk, we shall restore AutoRemove diff --git a/daemon/stop.go b/daemon/stop.go index fb9d1fc62c..67253e7770 100644 --- a/daemon/stop.go +++ b/daemon/stop.go @@ -4,6 +4,7 @@ import ( "context" "time" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/container" "github.com/docker/docker/errdefs" "github.com/pkg/errors" @@ -18,7 +19,7 @@ import ( // If the timeout is nil, the container's StopTimeout value is used, if set, // otherwise the engine default. A negative timeout value can be specified, // meaning no timeout, i.e. no forceful termination is performed. -func (daemon *Daemon) ContainerStop(name string, timeout *int) error { +func (daemon *Daemon) ContainerStop(ctx context.Context, name string, options containertypes.StopOptions) error { ctr, err := daemon.GetContainer(name) if err != nil { return err @@ -26,7 +27,7 @@ func (daemon *Daemon) ContainerStop(name string, timeout *int) error { if !ctr.IsRunning() { return containerNotModifiedError{} } - err = daemon.containerStop(ctr, timeout) + err = daemon.containerStop(ctx, ctr, options) if err != nil { return errdefs.System(errors.Wrapf(err, "cannot stop container: %s", name)) } @@ -34,9 +35,7 @@ func (daemon *Daemon) ContainerStop(name string, timeout *int) error { } // containerStop sends a stop signal, waits, sends a kill signal. -func (daemon *Daemon) containerStop(ctr *container.Container, seconds *int) (retErr error) { - // TODO propagate a context down to this function - ctx := context.TODO() +func (daemon *Daemon) containerStop(ctx context.Context, ctr *container.Container, options containertypes.StopOptions) (retErr error) { if !ctr.IsRunning() { return nil } @@ -45,8 +44,8 @@ func (daemon *Daemon) containerStop(ctr *container.Container, seconds *int) (ret stopSignal = ctr.StopSignal() stopTimeout = ctr.StopTimeout() ) - if seconds != nil { - stopTimeout = *seconds + if options.Timeout != nil { + stopTimeout = *options.Timeout } var wait time.Duration