github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/pkg/oom/v1/v1.go (about)

     1  // +build linux
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  package v1
    20  
    21  import (
    22  	"context"
    23  	"sync"
    24  
    25  	"github.com/containerd/cgroups"
    26  	eventstypes "github.com/containerd/containerd/api/events"
    27  	"github.com/containerd/containerd/pkg/oom"
    28  	"github.com/containerd/containerd/runtime"
    29  	"github.com/containerd/containerd/runtime/v2/shim"
    30  	"github.com/pkg/errors"
    31  	"github.com/sirupsen/logrus"
    32  	"golang.org/x/sys/unix"
    33  )
    34  
    35  // New returns an epoll implementation that listens to OOM events
    36  // from a container's cgroups.
    37  func New(publisher shim.Publisher) (oom.Watcher, error) {
    38  	fd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	return &epoller{
    43  		fd:        fd,
    44  		publisher: publisher,
    45  		set:       make(map[uintptr]*item),
    46  	}, nil
    47  }
    48  
    49  // epoller implementation for handling OOM events from a container's cgroup
    50  type epoller struct {
    51  	mu sync.Mutex
    52  
    53  	fd        int
    54  	publisher shim.Publisher
    55  	set       map[uintptr]*item
    56  }
    57  
    58  type item struct {
    59  	id string
    60  	cg cgroups.Cgroup
    61  }
    62  
    63  // Close the epoll fd
    64  func (e *epoller) Close() error {
    65  	return unix.Close(e.fd)
    66  }
    67  
    68  // Run the epoll loop
    69  func (e *epoller) Run(ctx context.Context) {
    70  	var events [128]unix.EpollEvent
    71  	for {
    72  		select {
    73  		case <-ctx.Done():
    74  			e.Close()
    75  			return
    76  		default:
    77  			n, err := unix.EpollWait(e.fd, events[:], -1)
    78  			if err != nil {
    79  				if err == unix.EINTR {
    80  					continue
    81  				}
    82  				logrus.WithError(err).Error("cgroups: epoll wait")
    83  			}
    84  			for i := 0; i < n; i++ {
    85  				e.process(ctx, uintptr(events[i].Fd))
    86  			}
    87  		}
    88  	}
    89  }
    90  
    91  // Add cgroups.Cgroup to the epoll monitor
    92  func (e *epoller) Add(id string, cgx interface{}) error {
    93  	cg, ok := cgx.(cgroups.Cgroup)
    94  	if !ok {
    95  		return errors.Errorf("expected cgroups.Cgroup, got: %T", cgx)
    96  	}
    97  	e.mu.Lock()
    98  	defer e.mu.Unlock()
    99  	fd, err := cg.OOMEventFD()
   100  	if err != nil {
   101  		return err
   102  	}
   103  	e.set[fd] = &item{
   104  		id: id,
   105  		cg: cg,
   106  	}
   107  	event := unix.EpollEvent{
   108  		Fd:     int32(fd),
   109  		Events: unix.EPOLLHUP | unix.EPOLLIN | unix.EPOLLERR,
   110  	}
   111  	return unix.EpollCtl(e.fd, unix.EPOLL_CTL_ADD, int(fd), &event)
   112  }
   113  
   114  func (e *epoller) process(ctx context.Context, fd uintptr) {
   115  	flush(fd)
   116  	e.mu.Lock()
   117  	i, ok := e.set[fd]
   118  	if !ok {
   119  		e.mu.Unlock()
   120  		return
   121  	}
   122  	e.mu.Unlock()
   123  	if i.cg.State() == cgroups.Deleted {
   124  		e.mu.Lock()
   125  		delete(e.set, fd)
   126  		e.mu.Unlock()
   127  		unix.Close(int(fd))
   128  		return
   129  	}
   130  	if err := e.publisher.Publish(ctx, runtime.TaskOOMEventTopic, &eventstypes.TaskOOM{
   131  		ContainerID: i.id,
   132  	}); err != nil {
   133  		logrus.WithError(err).Error("publish OOM event")
   134  	}
   135  }
   136  
   137  func flush(fd uintptr) error {
   138  	var buf [8]byte
   139  	_, err := unix.Read(int(fd), buf[:])
   140  	return err
   141  }