diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index 23484ed2e74..88f2349b31e 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -648,6 +648,13 @@ func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) { if c.config.RootlessCgroups { logrus.Warn("getting OOM notifications may fail if you don't have the full access to cgroups") } + if cgroups.IsCgroup2UnifiedMode() { + path, err := c.cgroupManager.GetUnifiedPath() + if err != nil { + return nil, err + } + return notifyOnOOMV2(path) + } return notifyOnOOM(c.cgroupManager.GetPaths()) } diff --git a/libcontainer/notify_linux_v2.go b/libcontainer/notify_linux_v2.go new file mode 100644 index 00000000000..a8b1d201f02 --- /dev/null +++ b/libcontainer/notify_linux_v2.go @@ -0,0 +1,101 @@ +// +build linux + +package libcontainer + +import ( + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + "unsafe" + + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +func getOOMCount(path string) (int, error) { + // When a cgroup is destroyed, an event is sent to eventfd. + // So if the control path is gone, return instead of notifying. + if _, err := os.Lstat(path); err != nil { + return 0, err + } + + content, err := ioutil.ReadFile(path) + if err != nil { + return 0, err + } + + oomCount := 0 + lines := strings.Split(string(content), "\n") + for _, line := range lines { + arr := strings.Fields(line) + if len(arr) == 2 && (arr[0] == "oom" || arr[0] == "oom_kill") { + count, err := strconv.Atoi(arr[1]) + if err == nil && count > oomCount { + oomCount = count + } + } + } + return oomCount, nil +} + +func registerMemoryEventV2(cgDir string, evName string) (<-chan struct{}, error) { + eventControlPath := filepath.Join(cgDir, evName) + fd, err := unix.InotifyInit() + if err != nil { + return nil, err + } + _, err = unix.InotifyAddWatch(fd, eventControlPath, unix.IN_MODIFY|unix.IN_DELETE|unix.IN_DELETE_SELF) + if err != nil { + unix.Close(fd) + return nil, err + } + ch := make(chan struct{}) + go func() { + var ( + buf [unix.SizeofInotifyEvent * 10]byte + n int + offset uint32 + ) + + defer func() { + unix.Close(fd) + close(ch) + }() + + for { + n, err = unix.Read(fd, buf[:]) + if err != nil { + logrus.Warn(err) + return + } + offset = 0 + for offset <= uint32(n-unix.SizeofInotifyEvent) { + raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset])) + mask := uint32(raw.Mask) + nameLen := uint32(raw.Len) + offset += unix.SizeofInotifyEvent + nameLen + if mask&unix.IN_MODIFY > 0 { + oomCount, err := getOOMCount(eventControlPath) + if err != nil { + return + } + if oomCount > 0 { + ch <- struct{}{} + return + } + } else if mask&unix.IN_DELETE > 0 || mask&unix.IN_DELETE_SELF > 0 { + return + } + } + } + }() + return ch, nil +} + +// notifyOnOOMV2 returns channel on which you can expect event about OOM, +// if process died without OOM this channel will be closed. +func notifyOnOOMV2(path string) (<-chan struct{}, error) { + return registerMemoryEventV2(path, "memory.events") +}