github.com/moby/docker@v26.1.3+incompatible/daemon/kill.go (about)

     1  package daemon // import "github.com/docker/docker/daemon"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"runtime"
     7  	"strconv"
     8  	"syscall"
     9  	"time"
    10  
    11  	"github.com/containerd/log"
    12  	"github.com/docker/docker/api/types/events"
    13  	containerpkg "github.com/docker/docker/container"
    14  	"github.com/docker/docker/errdefs"
    15  	"github.com/moby/sys/signal"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  type errNoSuchProcess struct {
    20  	pid    int
    21  	signal syscall.Signal
    22  }
    23  
    24  func (e errNoSuchProcess) Error() string {
    25  	return fmt.Sprintf("cannot kill process (pid=%d) with signal %d: no such process", e.pid, e.signal)
    26  }
    27  
    28  func (errNoSuchProcess) NotFound() {}
    29  
    30  // ContainerKill sends signal to the container
    31  // If no signal is given, then Kill with SIGKILL and wait
    32  // for the container to exit.
    33  // If a signal is given, then just send it to the container and return.
    34  func (daemon *Daemon) ContainerKill(name, stopSignal string) error {
    35  	var (
    36  		err error
    37  		sig = syscall.SIGKILL
    38  	)
    39  	if stopSignal != "" {
    40  		sig, err = signal.ParseSignal(stopSignal)
    41  		if err != nil {
    42  			return errdefs.InvalidParameter(err)
    43  		}
    44  		if !signal.ValidSignalForPlatform(sig) {
    45  			return errdefs.InvalidParameter(errors.Errorf("the %s daemon does not support signal %d", runtime.GOOS, sig))
    46  		}
    47  	}
    48  	container, err := daemon.GetContainer(name)
    49  	if err != nil {
    50  		return err
    51  	}
    52  	if sig == syscall.SIGKILL {
    53  		// perform regular Kill (SIGKILL + wait())
    54  		return daemon.Kill(container)
    55  	}
    56  	return daemon.killWithSignal(container, sig)
    57  }
    58  
    59  // killWithSignal sends the container the given signal. This wrapper for the
    60  // host specific kill command prepares the container before attempting
    61  // to send the signal. An error is returned if the container is paused
    62  // or not running, or if there is a problem returned from the
    63  // underlying kill command.
    64  func (daemon *Daemon) killWithSignal(container *containerpkg.Container, stopSignal syscall.Signal) error {
    65  	log.G(context.TODO()).Debugf("Sending kill signal %d to container %s", stopSignal, container.ID)
    66  	container.Lock()
    67  	defer container.Unlock()
    68  
    69  	task, err := container.GetRunningTask()
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	var unpause bool
    75  	if container.Config.StopSignal != "" && stopSignal != syscall.SIGKILL {
    76  		containerStopSignal, err := signal.ParseSignal(container.Config.StopSignal)
    77  		if err != nil {
    78  			return err
    79  		}
    80  		if containerStopSignal == stopSignal {
    81  			container.ExitOnNext()
    82  			unpause = container.Paused
    83  		}
    84  	} else {
    85  		container.ExitOnNext()
    86  		unpause = container.Paused
    87  	}
    88  
    89  	if !daemon.IsShuttingDown() {
    90  		container.HasBeenManuallyStopped = true
    91  		if err := container.CheckpointTo(daemon.containersReplica); err != nil {
    92  			log.G(context.TODO()).WithFields(log.Fields{
    93  				"error":     err,
    94  				"container": container.ID,
    95  			}).Warn("error checkpointing container state")
    96  		}
    97  	}
    98  
    99  	// if the container is currently restarting we do not need to send the signal
   100  	// to the process. Telling the monitor that it should exit on its next event
   101  	// loop is enough
   102  	if container.Restarting {
   103  		return nil
   104  	}
   105  
   106  	if err := task.Kill(context.Background(), stopSignal); err != nil {
   107  		if errdefs.IsNotFound(err) {
   108  			unpause = false
   109  			log.G(context.TODO()).WithError(err).WithField("container", container.ID).WithField("action", "kill").Debug("container kill failed because of 'container not found' or 'no such process'")
   110  			go func() {
   111  				// We need to clean up this container but it is possible there is a case where we hit here before the exit event is processed
   112  				// but after it was fired off.
   113  				// So let's wait the container's stop timeout amount of time to see if the event is eventually processed.
   114  				// Doing this has the side effect that if no event was ever going to come we are waiting a longer period of time unnecessarily.
   115  				// But this prevents race conditions in processing the container.
   116  				ctx, cancel := context.WithTimeout(context.TODO(), time.Duration(container.StopTimeout())*time.Second)
   117  				defer cancel()
   118  				s := <-container.Wait(ctx, containerpkg.WaitConditionNotRunning)
   119  				if s.Err() != nil {
   120  					if err := daemon.handleContainerExit(container, nil); err != nil {
   121  						log.G(context.TODO()).WithFields(log.Fields{
   122  							"error":     err,
   123  							"container": container.ID,
   124  							"action":    "kill",
   125  						}).Warn("error while handling container exit")
   126  					}
   127  				}
   128  			}()
   129  		} else {
   130  			return errors.Wrapf(err, "Cannot kill container %s", container.ID)
   131  		}
   132  	}
   133  
   134  	if unpause {
   135  		// above kill signal will be sent once resume is finished
   136  		if err := task.Resume(context.Background()); err != nil {
   137  			log.G(context.TODO()).Warnf("Cannot unpause container %s: %s", container.ID, err)
   138  		}
   139  	}
   140  
   141  	daemon.LogContainerEventWithAttributes(container, events.ActionKill, map[string]string{
   142  		"signal": strconv.Itoa(int(stopSignal)),
   143  	})
   144  	return nil
   145  }
   146  
   147  // Kill forcefully terminates a container.
   148  func (daemon *Daemon) Kill(container *containerpkg.Container) error {
   149  	if !container.IsRunning() {
   150  		return errNotRunning(container.ID)
   151  	}
   152  
   153  	// 1. Send SIGKILL
   154  	if err := daemon.killPossiblyDeadProcess(container, syscall.SIGKILL); err != nil {
   155  		// kill failed, check if process is no longer running.
   156  		if errors.As(err, &errNoSuchProcess{}) {
   157  			return nil
   158  		}
   159  	}
   160  
   161  	waitTimeout := 10 * time.Second
   162  	if runtime.GOOS == "windows" {
   163  		waitTimeout = 75 * time.Second // runhcs can be sloooooow.
   164  	}
   165  
   166  	ctx, cancel := context.WithTimeout(context.Background(), waitTimeout)
   167  	defer cancel()
   168  
   169  	status := <-container.Wait(ctx, containerpkg.WaitConditionNotRunning)
   170  	if status.Err() == nil {
   171  		return nil
   172  	}
   173  
   174  	log.G(ctx).WithFields(log.Fields{"error": status.Err(), "container": container.ID}).Warnf("Container failed to exit within %v of kill - trying direct SIGKILL", waitTimeout)
   175  
   176  	if err := killProcessDirectly(container); err != nil {
   177  		if errors.As(err, &errNoSuchProcess{}) {
   178  			return nil
   179  		}
   180  		return err
   181  	}
   182  
   183  	// wait for container to exit one last time, if it doesn't then kill didnt work, so return error
   184  	ctx2, cancel2 := context.WithTimeout(context.Background(), 2*time.Second)
   185  	defer cancel2()
   186  
   187  	if status := <-container.Wait(ctx2, containerpkg.WaitConditionNotRunning); status.Err() != nil {
   188  		return errors.New("tried to kill container, but did not receive an exit event")
   189  	}
   190  	return nil
   191  }
   192  
   193  // killPossiblyDeadProcess is a wrapper around killSig() suppressing "no such process" error.
   194  func (daemon *Daemon) killPossiblyDeadProcess(container *containerpkg.Container, sig syscall.Signal) error {
   195  	err := daemon.killWithSignal(container, sig)
   196  	if errdefs.IsNotFound(err) {
   197  		err = errNoSuchProcess{container.GetPID(), sig}
   198  		log.G(context.TODO()).Debug(err)
   199  		return err
   200  	}
   201  	return err
   202  }