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 }