@@ -42,6 +42,7 @@ import (
4242const (
4343 subtreeControl = "cgroup.subtree_control"
4444 controllersFile = "cgroup.controllers"
45+ killFile = "cgroup.kill"
4546 defaultCgroup2Path = "/sys/fs/cgroup"
4647 defaultSlice = "system.slice"
4748)
@@ -357,6 +358,86 @@ func (c *Manager) AddThread(tid uint64) error {
357358 return writeValues (c .path , []Value {v })
358359}
359360
361+ // Kill will try to forcibly exit all of the processes in the cgroup. This is
362+ // equivalent to sending a SIGKILL to every process. On kernels 5.14 and greater
363+ // this will use the cgroup.kill file, on anything that doesn't have the cgroup.kill
364+ // file, a manual process of freezing -> sending a SIGKILL to every process -> thawing
365+ // will be used.
366+ func (c * Manager ) Kill () error {
367+ v := Value {
368+ filename : killFile ,
369+ value : "1" ,
370+ }
371+ err := writeValues (c .path , []Value {v })
372+ if err == nil {
373+ return nil
374+ }
375+ logrus .Warnf ("falling back to slower kill implementation: %s" , err )
376+ // Fallback to slow method.
377+ return c .fallbackKill ()
378+ }
379+
380+ // fallbackKill is a slower fallback to the more modern (kernels 5.14+)
381+ // approach of writing to the cgroup.kill file. This is heavily pulled
382+ // from runc's same approach (in signalAllProcesses), with the only differences
383+ // being this is just tailored to the API exposed in this library, and we don't
384+ // need to care about signals other than SIGKILL.
385+ //
386+ // https://github.com/opencontainers/runc/blob/8da0a0b5675764feaaaaad466f6567a9983fcd08/libcontainer/init_linux.go#L523-L529
387+ func (c * Manager ) fallbackKill () error {
388+ if err := c .Freeze (); err != nil {
389+ logrus .Warn (err )
390+ }
391+ pids , err := c .Procs (true )
392+ if err != nil {
393+ if err := c .Thaw (); err != nil {
394+ logrus .Warn (err )
395+ }
396+ return err
397+ }
398+ var procs []* os.Process
399+ for _ , pid := range pids {
400+ p , err := os .FindProcess (int (pid ))
401+ if err != nil {
402+ logrus .Warn (err )
403+ continue
404+ }
405+ procs = append (procs , p )
406+ if err := p .Signal (unix .SIGKILL ); err != nil {
407+ logrus .Warn (err )
408+ }
409+ }
410+ if err := c .Thaw (); err != nil {
411+ logrus .Warn (err )
412+ }
413+
414+ subreaper , err := getSubreaper ()
415+ if err != nil {
416+ // The error here means that PR_GET_CHILD_SUBREAPER is not
417+ // supported because this code might run on a kernel older
418+ // than 3.4. We don't want to throw an error in that case,
419+ // and we simplify things, considering there is no subreaper
420+ // set.
421+ subreaper = 0
422+ }
423+
424+ for _ , p := range procs {
425+ // In case a subreaper has been setup, this code must not
426+ // wait for the process. Otherwise, we cannot be sure the
427+ // current process will be reaped by the subreaper, while
428+ // the subreaper might be waiting for this process in order
429+ // to retrieve its exit code.
430+ if subreaper == 0 {
431+ if _ , err := p .Wait (); err != nil {
432+ if ! errors .Is (err , unix .ECHILD ) {
433+ logrus .Warnf ("wait on pid %d failed: %s" , p .Pid , err )
434+ }
435+ }
436+ }
437+ }
438+ return nil
439+ }
440+
360441func (c * Manager ) Delete () error {
361442 // kernel prevents cgroups with running process from being removed, check the tree is empty
362443 processes , err := c .Procs (true )
0 commit comments