github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/daemon/stop.go (about)

     1  package daemon // import "github.com/docker/docker/daemon"
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	"github.com/containerd/log"
     8  	containertypes "github.com/docker/docker/api/types/container"
     9  	"github.com/docker/docker/api/types/events"
    10  	"github.com/docker/docker/container"
    11  	"github.com/docker/docker/errdefs"
    12  	"github.com/docker/docker/internal/compatcontext"
    13  	"github.com/moby/sys/signal"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  // ContainerStop looks for the given container and stops it.
    18  // In case the container fails to stop gracefully within a time duration
    19  // specified by the timeout argument, in seconds, it is forcefully
    20  // terminated (killed).
    21  //
    22  // If the timeout is nil, the container's StopTimeout value is used, if set,
    23  // otherwise the engine default. A negative timeout value can be specified,
    24  // meaning no timeout, i.e. no forceful termination is performed.
    25  func (daemon *Daemon) ContainerStop(ctx context.Context, name string, options containertypes.StopOptions) error {
    26  	ctr, err := daemon.GetContainer(name)
    27  	if err != nil {
    28  		return err
    29  	}
    30  	if !ctr.IsRunning() {
    31  		// This is not an actual error, but produces a 304 "not modified"
    32  		// when returned through the API to indicates the container is
    33  		// already in the desired state. It's implemented as an error
    34  		// to make the code calling this function terminate early (as
    35  		// no further processing is needed).
    36  		return errdefs.NotModified(errors.New("container is already stopped"))
    37  	}
    38  	err = daemon.containerStop(ctx, ctr, options)
    39  	if err != nil {
    40  		return errdefs.System(errors.Wrapf(err, "cannot stop container: %s", name))
    41  	}
    42  	return nil
    43  }
    44  
    45  // containerStop sends a stop signal, waits, sends a kill signal. It uses
    46  // a [context.WithoutCancel], so cancelling the context does not cancel
    47  // the request to stop the container.
    48  func (daemon *Daemon) containerStop(ctx context.Context, ctr *container.Container, options containertypes.StopOptions) (retErr error) {
    49  	// Cancelling the request should not cancel the stop.
    50  	ctx = compatcontext.WithoutCancel(ctx)
    51  
    52  	if !ctr.IsRunning() {
    53  		return nil
    54  	}
    55  
    56  	var (
    57  		stopSignal  = ctr.StopSignal()
    58  		stopTimeout = ctr.StopTimeout()
    59  	)
    60  	if options.Signal != "" {
    61  		sig, err := signal.ParseSignal(options.Signal)
    62  		if err != nil {
    63  			return errdefs.InvalidParameter(err)
    64  		}
    65  		stopSignal = sig
    66  	}
    67  	if options.Timeout != nil {
    68  		stopTimeout = *options.Timeout
    69  	}
    70  
    71  	var wait time.Duration
    72  	if stopTimeout >= 0 {
    73  		wait = time.Duration(stopTimeout) * time.Second
    74  	}
    75  	defer func() {
    76  		if retErr == nil {
    77  			daemon.LogContainerEvent(ctr, events.ActionStop)
    78  		}
    79  	}()
    80  
    81  	// 1. Send a stop signal
    82  	err := daemon.killPossiblyDeadProcess(ctr, stopSignal)
    83  	if err != nil {
    84  		wait = 2 * time.Second
    85  	}
    86  
    87  	var subCtx context.Context
    88  	var cancel context.CancelFunc
    89  	if stopTimeout >= 0 {
    90  		subCtx, cancel = context.WithTimeout(ctx, wait)
    91  	} else {
    92  		subCtx, cancel = context.WithCancel(ctx)
    93  	}
    94  	defer cancel()
    95  
    96  	if status := <-ctr.Wait(subCtx, container.WaitConditionNotRunning); status.Err() == nil {
    97  		// container did exit, so ignore any previous errors and return
    98  		return nil
    99  	}
   100  
   101  	if err != nil {
   102  		// the container has still not exited, and the kill function errored, so log the error here:
   103  		log.G(ctx).WithError(err).WithField("container", ctr.ID).Errorf("Error sending stop (signal %d) to container", stopSignal)
   104  	}
   105  	if stopTimeout < 0 {
   106  		// if the client requested that we never kill / wait forever, but container.Wait was still
   107  		// interrupted (parent context cancelled, for example), we should propagate the signal failure
   108  		return err
   109  	}
   110  
   111  	log.G(ctx).WithField("container", ctr.ID).Infof("Container failed to exit within %s of signal %d - using the force", wait, stopSignal)
   112  
   113  	// Stop either failed or container didn't exit, so fallback to kill.
   114  	if err := daemon.Kill(ctr); err != nil {
   115  		// got a kill error, but give container 2 more seconds to exit just in case
   116  		subCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
   117  		defer cancel()
   118  		status := <-ctr.Wait(subCtx, container.WaitConditionNotRunning)
   119  		if status.Err() != nil {
   120  			log.G(ctx).WithError(err).WithField("container", ctr.ID).Errorf("error killing container: %v", status.Err())
   121  			return err
   122  		}
   123  		// container did exit, so ignore previous errors and continue
   124  	}
   125  
   126  	return nil
   127  }