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 }