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 }