e578b2510a
Commit90de570cfa
passed through the request context to daemon.ContainerStop(). As a result, cancelling the context would cancel the "graceful" stop of the container, and would proceed with forcefully killing the container. This patch partially reverts the changes from90de570cfa
and breaks the context to prevent cancelling the context from cancelling the stop. Signed-off-by: Sebastiaan van Stijn <github@gone.nl> Signed-off-by: Sebastiaan van Stijn <github@gone.nl> (cherry picked from commitfc94ed0a86
) Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
121 lines
3.8 KiB
Go
121 lines
3.8 KiB
Go
package daemon // import "github.com/docker/docker/daemon"
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
containertypes "github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/container"
|
|
"github.com/docker/docker/errdefs"
|
|
"github.com/moby/sys/signal"
|
|
"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(ctx context.Context, name string, options containertypes.StopOptions) error {
|
|
ctr, err := daemon.GetContainer(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ctr.IsRunning() {
|
|
return containerNotModifiedError{}
|
|
}
|
|
err = daemon.containerStop(ctx, ctr, options)
|
|
if 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(_ context.Context, ctr *container.Container, options containertypes.StopOptions) (retErr error) {
|
|
// Deliberately using a local context here, because cancelling the
|
|
// request should not cancel the stop.
|
|
//
|
|
// TODO(thaJeztah): pass context, and use context.WithoutCancel() once available: https://github.com/golang/go/issues/40221
|
|
ctx := context.Background()
|
|
|
|
if !ctr.IsRunning() {
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
stopSignal = ctr.StopSignal()
|
|
stopTimeout = ctr.StopTimeout()
|
|
)
|
|
if options.Signal != "" {
|
|
sig, err := signal.ParseSignal(options.Signal)
|
|
if err != nil {
|
|
return errdefs.InvalidParameter(err)
|
|
}
|
|
stopSignal = sig
|
|
}
|
|
if options.Timeout != nil {
|
|
stopTimeout = *options.Timeout
|
|
}
|
|
|
|
var wait time.Duration
|
|
if stopTimeout >= 0 {
|
|
wait = time.Duration(stopTimeout) * time.Second
|
|
}
|
|
defer func() {
|
|
if retErr == nil {
|
|
daemon.LogContainerEvent(ctr, "stop")
|
|
}
|
|
}()
|
|
|
|
// 1. Send a stop signal
|
|
err := daemon.killPossiblyDeadProcess(ctr, stopSignal)
|
|
if err != nil {
|
|
wait = 2 * time.Second
|
|
}
|
|
|
|
var subCtx context.Context
|
|
var cancel context.CancelFunc
|
|
if stopTimeout >= 0 {
|
|
subCtx, cancel = context.WithTimeout(ctx, wait)
|
|
} else {
|
|
subCtx, cancel = context.WithCancel(ctx)
|
|
}
|
|
defer cancel()
|
|
|
|
if status := <-ctr.Wait(subCtx, container.WaitConditionNotRunning); status.Err() == nil {
|
|
// container did exit, so ignore any previous errors and return
|
|
return nil
|
|
}
|
|
|
|
if err != nil {
|
|
// the container has still not exited, and the kill function errored, so log the error here:
|
|
logrus.WithError(err).WithField("container", ctr.ID).Errorf("Error sending stop (signal %d) to container", stopSignal)
|
|
}
|
|
if stopTimeout < 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", ctr.ID).Infof("Container failed to exit within %s of signal %d - using the force", wait, stopSignal)
|
|
|
|
// Stop either failed or container didn't exit, so fallback to kill.
|
|
if err := daemon.Kill(ctr); 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()
|
|
status := <-ctr.Wait(subCtx, container.WaitConditionNotRunning)
|
|
if status.Err() != nil {
|
|
logrus.WithError(err).WithField("container", ctr.ID).Errorf("error killing container: %v", status.Err())
|
|
return err
|
|
}
|
|
// container did exit, so ignore previous errors and continue
|
|
}
|
|
|
|
return nil
|
|
}
|