package daemon // import "github.com/docker/docker/daemon" import ( "context" "time" containerpkg "github.com/docker/docker/container" "github.com/docker/docker/errdefs" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) // ContainerStop looks for the given container and stops it. // In case the container fails to stop gracefully within a time duration // specified by the timeout argument, in seconds, it is forcefully // terminated (killed). // // 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 { container, err := daemon.GetContainer(name) if err != nil { return err } if !container.IsRunning() { return containerNotModifiedError{running: false} } if timeout == nil { stopTimeout := container.StopTimeout() timeout = &stopTimeout } if err := daemon.containerStop(container, *timeout); err != nil { return errdefs.System(errors.Wrapf(err, "cannot stop container: %s", name)) } return nil } // containerStop sends a stop signal, waits, sends a kill signal. func (daemon *Daemon) containerStop(container *containerpkg.Container, seconds int) error { // TODO propagate a context down to this function ctx := context.TODO() if !container.IsRunning() { return nil } var wait time.Duration if seconds >= 0 { wait = time.Duration(seconds) * time.Second } success := func() error { daemon.LogContainerEvent(container, "stop") return nil } stopSignal := container.StopSignal() // 1. Send a stop signal err := daemon.killPossiblyDeadProcess(container, stopSignal) if err != nil { wait = 2 * time.Second } var subCtx context.Context var cancel context.CancelFunc if seconds >= 0 { subCtx, cancel = context.WithTimeout(ctx, wait) } else { subCtx, cancel = context.WithCancel(ctx) } defer cancel() if status := <-container.Wait(subCtx, containerpkg.WaitConditionNotRunning); status.Err() == nil { // container did exit, so ignore any previous errors and return return success() } if err != nil { // the container has still not exited, and the kill function errored, so log the error here: logrus.WithError(err).WithField("container", container.ID).Errorf("Error sending stop (signal %d) to container", stopSignal) } if seconds < 0 { // if the client requested that we never kill / wait forever, but container.Wait was still // interrupted (parent context cancelled, for example), we should propagate the signal failure return err } logrus.WithField("container", container.ID).Infof("Container failed to exit within %s of signal %d - using the force", wait, stopSignal) // Stop either failed or container didnt exit, so fallback to kill. if err := daemon.Kill(container); err != nil { // got a kill error, but give container 2 more seconds to exit just in case subCtx, cancel := context.WithTimeout(ctx, 2*time.Second) defer cancel() if status := <-container.Wait(subCtx, containerpkg.WaitConditionNotRunning); status.Err() == nil { // container did exit, so ignore error and return return success() } logrus.WithError(err).WithField("container", container.ID).Error("Error killing the container") return err } return success() }