@@ -42,6 +42,7 @@ import (
42
42
const (
43
43
subtreeControl = "cgroup.subtree_control"
44
44
controllersFile = "cgroup.controllers"
45
+ killFile = "cgroup.kill"
45
46
defaultCgroup2Path = "/sys/fs/cgroup"
46
47
defaultSlice = "system.slice"
47
48
)
@@ -357,6 +358,86 @@ func (c *Manager) AddThread(tid uint64) error {
357
358
return writeValues (c .path , []Value {v })
358
359
}
359
360
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
+
360
441
func (c * Manager ) Delete () error {
361
442
// kernel prevents cgroups with running process from being removed, check the tree is empty
362
443
processes , err := c .Procs (true )
0 commit comments