1// +build linux
2
3package libcontainer
4
5import (
6	"errors"
7	"fmt"
8	"io/ioutil"
9	"os"
10	"path/filepath"
11
12	"golang.org/x/sys/unix"
13)
14
15type PressureLevel uint
16
17const (
18	LowPressure PressureLevel = iota
19	MediumPressure
20	CriticalPressure
21)
22
23func registerMemoryEvent(cgDir string, evName string, arg string) (<-chan struct{}, error) {
24	evFile, err := os.Open(filepath.Join(cgDir, evName))
25	if err != nil {
26		return nil, err
27	}
28	fd, err := unix.Eventfd(0, unix.EFD_CLOEXEC)
29	if err != nil {
30		evFile.Close()
31		return nil, err
32	}
33
34	eventfd := os.NewFile(uintptr(fd), "eventfd")
35
36	eventControlPath := filepath.Join(cgDir, "cgroup.event_control")
37	data := fmt.Sprintf("%d %d %s", eventfd.Fd(), evFile.Fd(), arg)
38	if err := ioutil.WriteFile(eventControlPath, []byte(data), 0o700); err != nil {
39		eventfd.Close()
40		evFile.Close()
41		return nil, err
42	}
43	ch := make(chan struct{})
44	go func() {
45		defer func() {
46			eventfd.Close()
47			evFile.Close()
48			close(ch)
49		}()
50		buf := make([]byte, 8)
51		for {
52			if _, err := eventfd.Read(buf); err != nil {
53				return
54			}
55			// When a cgroup is destroyed, an event is sent to eventfd.
56			// So if the control path is gone, return instead of notifying.
57			if _, err := os.Lstat(eventControlPath); os.IsNotExist(err) {
58				return
59			}
60			ch <- struct{}{}
61		}
62	}()
63	return ch, nil
64}
65
66// notifyOnOOM returns channel on which you can expect event about OOM,
67// if process died without OOM this channel will be closed.
68func notifyOnOOM(dir string) (<-chan struct{}, error) {
69	if dir == "" {
70		return nil, errors.New("memory controller missing")
71	}
72
73	return registerMemoryEvent(dir, "memory.oom_control", "")
74}
75
76func notifyMemoryPressure(dir string, level PressureLevel) (<-chan struct{}, error) {
77	if dir == "" {
78		return nil, errors.New("memory controller missing")
79	}
80
81	if level > CriticalPressure {
82		return nil, fmt.Errorf("invalid pressure level %d", level)
83	}
84
85	levelStr := []string{"low", "medium", "critical"}[level]
86	return registerMemoryEvent(dir, "memory.pressure_level", levelStr)
87}
88