File: notify_linux_v2.go

package info (click to toggle)
runc 1.0.0~rc93%2Bds1-5
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, sid
  • size: 3,172 kB
  • sloc: sh: 1,679; ansic: 1,039; makefile: 139
file content (102 lines) | stat: -rw-r--r-- 2,679 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// +build linux

package libcontainer

import (
	"io/ioutil"
	"path/filepath"
	"strconv"
	"strings"
	"unsafe"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
)

func getValueFromCgroup(path, key string) (int, error) {
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return 0, err
	}

	lines := strings.Split(string(content), "\n")
	for _, line := range lines {
		arr := strings.Split(line, " ")
		if len(arr) == 2 && arr[0] == key {
			return strconv.Atoi(arr[1])
		}
	}
	return 0, nil
}

func registerMemoryEventV2(cgDir, evName, cgEvName string) (<-chan struct{}, error) {
	eventControlPath := filepath.Join(cgDir, evName)
	cgEvPath := filepath.Join(cgDir, cgEvName)
	fd, err := unix.InotifyInit()
	if err != nil {
		return nil, errors.Wrap(err, "unable to init inotify")
	}
	// watching oom kill
	evFd, err := unix.InotifyAddWatch(fd, eventControlPath, unix.IN_MODIFY)
	if err != nil {
		unix.Close(fd)
		return nil, errors.Wrap(err, "unable to add inotify watch")
	}
	// Because no `unix.IN_DELETE|unix.IN_DELETE_SELF` event for cgroup file system, so watching all process exited
	cgFd, err := unix.InotifyAddWatch(fd, cgEvPath, unix.IN_MODIFY)
	if err != nil {
		unix.Close(fd)
		return nil, errors.Wrap(err, "unable to add inotify watch")
	}
	ch := make(chan struct{})
	go func() {
		var (
			buffer [unix.SizeofInotifyEvent + unix.PathMax + 1]byte
			offset uint32
		)
		defer func() {
			unix.Close(fd)
			close(ch)
		}()

		for {
			n, err := unix.Read(fd, buffer[:])
			if err != nil {
				logrus.Warnf("unable to read event data from inotify, got error: %v", err)
				return
			}
			if n < unix.SizeofInotifyEvent {
				logrus.Warnf("we should read at least %d bytes from inotify, but got %d bytes.", unix.SizeofInotifyEvent, n)
				return
			}
			offset = 0
			for offset <= uint32(n-unix.SizeofInotifyEvent) {
				rawEvent := (*unix.InotifyEvent)(unsafe.Pointer(&buffer[offset]))
				offset += unix.SizeofInotifyEvent + uint32(rawEvent.Len)
				if rawEvent.Mask&unix.IN_MODIFY != unix.IN_MODIFY {
					continue
				}
				switch int(rawEvent.Wd) {
				case evFd:
					oom, err := getValueFromCgroup(eventControlPath, "oom_kill")
					if err != nil || oom > 0 {
						ch <- struct{}{}
					}
				case cgFd:
					pids, err := getValueFromCgroup(cgEvPath, "populated")
					if err != nil || pids == 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", "cgroup.events")
}