dotcloud/docker

View on GitHub
daemon/stop.go

Summary

Maintainability
A
2 hrs
Test Coverage
package daemon // import "github.com/docker/docker/daemon"

import (
    "context"
    "time"

    "github.com/containerd/log"
    containertypes "github.com/docker/docker/api/types/container"
    "github.com/docker/docker/api/types/events"
    "github.com/docker/docker/container"
    "github.com/docker/docker/errdefs"
    "github.com/moby/sys/signal"
    "github.com/pkg/errors"
)

// 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() {
        // This is not an actual error, but produces a 304 "not modified"
        // when returned through the API to indicates the container is
        // already in the desired state. It's implemented as an error
        // to make the code calling this function terminate early (as
        // no further processing is needed).
        return errdefs.NotModified(errors.New("container is already stopped"))
    }
    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. It uses
// a [context.WithoutCancel], so cancelling the context does not cancel
// the request to stop the container.
func (daemon *Daemon) containerStop(ctx context.Context, ctr *container.Container, options containertypes.StopOptions) (retErr error) {
    // Cancelling the request should not cancel the stop.
    ctx = context.WithoutCancel(ctx)

    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, events.ActionStop)
        }
    }()

    // 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:
        log.G(ctx).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
    }

    log.G(ctx).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 {
            log.G(ctx).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
}