stop.go 3.8 KB

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