Skip to content

Commit d3fd53c

Browse files
committed
runc kill: add support for cgroup.kill
cgroup.kill inteface was added to Linux kernel 5.14 (see [1], [2]). Use it if we can. [1] https://lwn.net/Articles/855049/ [2] https://lwn.net/Articles/855924/ Signed-off-by: Kir Kolyshkin <[email protected]>
1 parent a4a694d commit d3fd53c

File tree

1 file changed

+93
-15
lines changed

1 file changed

+93
-15
lines changed

libcontainer/signal_all_linux.go

Lines changed: 93 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package libcontainer
22

33
import (
44
"errors"
5+
"fmt"
56
"os"
67
"unsafe"
78

@@ -34,36 +35,57 @@ func isWaitable(pid int) (bool, error) {
3435
return si.pid != 0, nil
3536
}
3637

37-
// signalAllProcesses freezes then iterates over all the processes inside the
38-
// manager's cgroups sending the signal s to them.
39-
// If s is SIGKILL and subreaper is not enabled then it will wait for each
40-
// process to exit.
41-
// For all other signals it will check if the process is ready to report its
42-
// exit status and only if it is will a wait be performed.
38+
// signalAllProcesses sends signal to all the process inside the manager's cgroup.
39+
// In case the signal is SIGKILL, and cgroup.kill is available, it is used. Otherwise,
40+
// the cgroup is frozen, then the signal is sent to all the processes one by one.
41+
//
42+
// If s is SIGKILL and subreaper is not enabled, this function waits for each
43+
// process to exit. For all other signals it will check if the process is ready
44+
// to report its exit status and only if it is will a wait be performed.
4345
func signalAllProcesses(m cgroups.Manager, s os.Signal) error {
4446
sig, ok := s.(unix.Signal)
4547
if !ok {
4648
return errors.New("unsupported signal type")
4749
}
4850

49-
if err := m.Freeze(configs.Frozen); err != nil {
50-
logrus.Warn(err)
51+
haveCgroupKill := false
52+
53+
// Use cgroup.kill, if available.
54+
if s == unix.SIGKILL {
55+
if p := m.Path(""); p != "" { // Either cgroup v2 or hybrid.
56+
if err := cgroupKillAll(p); err == nil {
57+
haveCgroupKill = true
58+
} else if !errors.Is(err, unix.ENOENT) {
59+
logrus.Warnf("cgroupKillAll: %v", err)
60+
}
61+
}
62+
}
63+
64+
if !haveCgroupKill {
65+
if err := m.Freeze(configs.Frozen); err != nil {
66+
logrus.Warn(err)
67+
}
5168
}
69+
5270
pids, err := m.GetAllPids()
5371
if err != nil {
54-
if err := m.Freeze(configs.Thawed); err != nil {
55-
logrus.Warn(err)
72+
if !haveCgroupKill {
73+
if err := m.Freeze(configs.Thawed); err != nil {
74+
logrus.Warn(err)
75+
}
5676
}
5777
return err
5878
}
59-
for _, pid := range pids {
60-
if err := unix.Kill(pid, sig); err != nil && err != unix.ESRCH { //nolint:errorlint // unix errors are bare
79+
if !haveCgroupKill {
80+
for _, pid := range pids {
81+
if err := unix.Kill(pid, sig); err != nil && err != unix.ESRCH { //nolint:errorlint // unix errors are bare
82+
logrus.Warn(err)
83+
}
84+
}
85+
if err := m.Freeze(configs.Thawed); err != nil {
6186
logrus.Warn(err)
6287
}
6388
}
64-
if err := m.Freeze(configs.Thawed); err != nil {
65-
logrus.Warn(err)
66-
}
6789

6890
subreaper, err := system.GetSubreaper()
6991
if err != nil {
@@ -103,3 +125,59 @@ func signalAllProcesses(m cgroups.Manager, s os.Signal) error {
103125
}
104126
return nil
105127
}
128+
129+
func prepareCgWait(dir string) (int, error) {
130+
fd, err := unix.InotifyInit()
131+
if err != nil {
132+
return -1, fmt.Errorf("unable to init inotify: %w", err)
133+
}
134+
_, err = unix.InotifyAddWatch(fd, dir+"/cgroup.events", unix.IN_MODIFY)
135+
if err != nil {
136+
unix.Close(fd)
137+
return -1, fmt.Errorf("unable to add inotify watch: %w", err)
138+
}
139+
return fd, nil
140+
}
141+
142+
func cgWait(fd int) error {
143+
fds := []unix.PollFd{{
144+
Fd: int32(fd),
145+
Events: unix.POLLIN,
146+
}}
147+
for {
148+
res, err := unix.Poll(fds, 10000)
149+
if err == unix.EINTR { //nolint:errorlint // unix errors are bare
150+
151+
continue
152+
}
153+
if err != nil {
154+
return &os.SyscallError{Syscall: "poll", Err: err}
155+
}
156+
if res == 0 { // Timeout.
157+
return &os.SyscallError{Syscall: "poll", Err: unix.ETIMEDOUT}
158+
}
159+
if res > 0 && fds[0].Revents&unix.POLLIN != 0 {
160+
return nil
161+
}
162+
}
163+
}
164+
165+
func cgroupKillAll(path string) error {
166+
const file = "cgroup.kill"
167+
if err := unix.Access(path+"/"+file, unix.F_OK); err != nil {
168+
return &os.PathError{Op: "access", Path: path + "/" + file, Err: err}
169+
}
170+
171+
fd, err := prepareCgWait(path)
172+
if err != nil {
173+
return err
174+
}
175+
176+
err = cgroups.WriteFile(path, file, "1")
177+
if err == nil {
178+
err = cgWait(fd)
179+
}
180+
_ = unix.Close(fd)
181+
182+
return err
183+
}

0 commit comments

Comments
 (0)