github.com/rawahars/moby@v24.0.4+incompatible/daemon/stop.go (about)

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