github.com/rumpl/bof@v23.0.0-rc.2+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  	containerpkg "github.com/docker/docker/container"
    12  	"github.com/docker/docker/errdefs"
    13  	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
    14  	"github.com/moby/sys/signal"
    15  	"github.com/pkg/errors"
    16  	"github.com/sirupsen/logrus"
    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  	logrus.Debugf("Sending kill signal %d to container %s", stopSignal, container.ID)
    66  	container.Lock()
    67  	defer container.Unlock()
    68  
    69  	if !container.Running {
    70  		return errNotRunning(container.ID)
    71  	}
    72  
    73  	var unpause bool
    74  	if container.Config.StopSignal != "" && stopSignal != syscall.SIGKILL {
    75  		containerStopSignal, err := signal.ParseSignal(container.Config.StopSignal)
    76  		if err != nil {
    77  			return err
    78  		}
    79  		if containerStopSignal == stopSignal {
    80  			container.ExitOnNext()
    81  			unpause = container.Paused
    82  		}
    83  	} else {
    84  		container.ExitOnNext()
    85  		unpause = container.Paused
    86  	}
    87  
    88  	if !daemon.IsShuttingDown() {
    89  		container.HasBeenManuallyStopped = true
    90  		container.CheckpointTo(daemon.containersReplica)
    91  	}
    92  
    93  	// if the container is currently restarting we do not need to send the signal
    94  	// to the process. Telling the monitor that it should exit on its next event
    95  	// loop is enough
    96  	if container.Restarting {
    97  		return nil
    98  	}
    99  
   100  	err := daemon.containerd.SignalProcess(context.Background(), container.ID, libcontainerdtypes.InitProcessName, stopSignal)
   101  	if err != nil {
   102  		if errdefs.IsNotFound(err) {
   103  			unpause = false
   104  			logrus.WithError(err).WithField("container", container.ID).WithField("action", "kill").Debug("container kill failed because of 'container not found' or 'no such process'")
   105  			go func() {
   106  				// 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
   107  				// but after it was fired off.
   108  				// So let's wait the container's stop timeout amount of time to see if the event is eventually processed.
   109  				// Doing this has the side effect that if no event was ever going to come we are waiting a a longer period of time uneccessarily.
   110  				// But this prevents race conditions in processing the container.
   111  				ctx, cancel := context.WithTimeout(context.TODO(), time.Duration(container.StopTimeout())*time.Second)
   112  				defer cancel()
   113  				s := <-container.Wait(ctx, containerpkg.WaitConditionNotRunning)
   114  				if s.Err() != nil {
   115  					daemon.handleContainerExit(container, nil)
   116  				}
   117  			}()
   118  		} else {
   119  			return errors.Wrapf(err, "Cannot kill container %s", container.ID)
   120  		}
   121  	}
   122  
   123  	if unpause {
   124  		// above kill signal will be sent once resume is finished
   125  		if err := daemon.containerd.Resume(context.Background(), container.ID); err != nil {
   126  			logrus.Warnf("Cannot unpause container %s: %s", container.ID, err)
   127  		}
   128  	}
   129  
   130  	daemon.LogContainerEventWithAttributes(container, "kill", map[string]string{
   131  		"signal": strconv.Itoa(int(stopSignal)),
   132  	})
   133  	return nil
   134  }
   135  
   136  // Kill forcefully terminates a container.
   137  func (daemon *Daemon) Kill(container *containerpkg.Container) error {
   138  	if !container.IsRunning() {
   139  		return errNotRunning(container.ID)
   140  	}
   141  
   142  	// 1. Send SIGKILL
   143  	if err := daemon.killPossiblyDeadProcess(container, syscall.SIGKILL); err != nil {
   144  		// kill failed, check if process is no longer running.
   145  		if errors.As(err, &errNoSuchProcess{}) {
   146  			return nil
   147  		}
   148  	}
   149  
   150  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   151  	defer cancel()
   152  
   153  	status := <-container.Wait(ctx, containerpkg.WaitConditionNotRunning)
   154  	if status.Err() == nil {
   155  		return nil
   156  	}
   157  
   158  	logrus.WithError(status.Err()).WithField("container", container.ID).Error("Container failed to exit within 10 seconds of kill - trying direct SIGKILL")
   159  
   160  	if err := killProcessDirectly(container); err != nil {
   161  		if errors.As(err, &errNoSuchProcess{}) {
   162  			return nil
   163  		}
   164  		return err
   165  	}
   166  
   167  	// wait for container to exit one last time, if it doesn't then kill didnt work, so return error
   168  	ctx2, cancel2 := context.WithTimeout(context.Background(), 2*time.Second)
   169  	defer cancel2()
   170  
   171  	if status := <-container.Wait(ctx2, containerpkg.WaitConditionNotRunning); status.Err() != nil {
   172  		return errors.New("tried to kill container, but did not receive an exit event")
   173  	}
   174  	return nil
   175  }
   176  
   177  // killPossibleDeadProcess is a wrapper around killSig() suppressing "no such process" error.
   178  func (daemon *Daemon) killPossiblyDeadProcess(container *containerpkg.Container, sig syscall.Signal) error {
   179  	err := daemon.killWithSignal(container, sig)
   180  	if errdefs.IsNotFound(err) {
   181  		err = errNoSuchProcess{container.GetPID(), sig}
   182  		logrus.Debug(err)
   183  		return err
   184  	}
   185  	return err
   186  }