github.com/openebs/node-disk-manager@v1.9.1-0.20230225014141-4531f06ffa1e/pkg/epoll/epoll.go (about)

     1  /*
     2  Copyright 2020 The OpenEBS Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package epoll
    18  
    19  import (
    20  	"errors"
    21  	"os"
    22  	"syscall"
    23  
    24  	"k8s.io/klog/v2"
    25  )
    26  
    27  const (
    28  	EPOLLIN  EventType = syscall.EPOLLIN
    29  	EPOLLPRI EventType = syscall.EPOLLPRI
    30  	EPOLLERR EventType = syscall.EPOLLERR
    31  	EPOLLOUT EventType = syscall.EPOLLOUT
    32  	EPOLLHUP EventType = syscall.EPOLLHUP
    33  
    34  	// timeout is the duration in milliseconds after which the EpollWait system
    35  	// call returns even if there are no events. Setting it to -1 makes the call
    36  	// return only when there is an event.
    37  	timeout int = -1
    38  )
    39  
    40  var (
    41  	ErrFileAlreadyWatched error = errors.New("file being watched already")
    42  	ErrInvalidEventType   error = errors.New("invalid event type")
    43  	ErrWatcherNotFound    error = errors.New("watcher not found")
    44  )
    45  
    46  type Epoll struct {
    47  	watchers      []Watcher
    48  	fileToWatcher map[string]*Watcher
    49  	fdToWatcher   map[int32]*Watcher
    50  	eventChan     chan Event
    51  	eventChanSize int
    52  	epfd          int
    53  	active        bool
    54  }
    55  
    56  type Event struct {
    57  	fileName string
    58  	events   uint32
    59  }
    60  
    61  type EventType uint32
    62  type Watcher struct {
    63  	fd *os.File
    64  	// The complete path of the file to watch. The file name is also used to
    65  	// uniquely identify the watcher
    66  	FileName string
    67  	// The event types to watch for
    68  	EventTypes []EventType
    69  }
    70  
    71  type NewOpt func(*Epoll)
    72  
    73  func BufferSize(size int) NewOpt {
    74  	return func(e *Epoll) {
    75  		e.eventChanSize = size
    76  	}
    77  }
    78  
    79  func New(opts ...NewOpt) (Epoll, error) {
    80  	epfd, err := syscall.EpollCreate(1)
    81  	if err != nil {
    82  		return Epoll{}, err
    83  	}
    84  	e := Epoll{
    85  		watchers:      make([]Watcher, 0),
    86  		fileToWatcher: make(map[string]*Watcher),
    87  		fdToWatcher:   make(map[int32]*Watcher),
    88  		epfd:          epfd,
    89  		eventChanSize: 0,
    90  	}
    91  	for _, opt := range opts {
    92  		opt(&e)
    93  	}
    94  	return e, nil
    95  }
    96  
    97  func (e *Epoll) Start() (<-chan Event, error) {
    98  	if e.epfd == 0 {
    99  		return nil, errors.New("invalid epoll struct")
   100  	}
   101  
   102  	if e.active {
   103  		return nil, errors.New("epoll already started")
   104  	}
   105  	e.eventChan = make(chan Event, e.eventChanSize)
   106  	e.active = true
   107  	go e.listen()
   108  	return e.eventChan, nil
   109  }
   110  
   111  func (e *Epoll) Stop() {
   112  	if !e.active {
   113  		return
   114  	}
   115  	e.active = false
   116  	close(e.eventChan)
   117  }
   118  
   119  func (e *Epoll) Close() {
   120  	e.Stop()
   121  	syscall.Close(e.epfd)
   122  	for i := range e.watchers {
   123  		e.watchers[i].fd.Close()
   124  	}
   125  	e.fdToWatcher = nil
   126  	e.fileToWatcher = nil
   127  	e.eventChan = nil
   128  	e.watchers = nil
   129  }
   130  
   131  func (e *Epoll) AddWatcher(w Watcher) error {
   132  	for _, evt := range w.EventTypes {
   133  		if !isValidEventType(evt) {
   134  			return ErrInvalidEventType
   135  		}
   136  	}
   137  
   138  	if _, ok := e.fileToWatcher[w.FileName]; ok {
   139  		return ErrFileAlreadyWatched
   140  	}
   141  	fd, err := os.Open(w.FileName)
   142  	if err != nil {
   143  		return err
   144  	}
   145  	w.fd = fd
   146  	if err = e.addToEpoll(fd, w.EventTypes); err != nil {
   147  		return err
   148  	}
   149  
   150  	e.watchers = append(e.watchers, w)
   151  	n := len(e.watchers) - 1
   152  	e.fileToWatcher[w.FileName] = &e.watchers[n]
   153  	e.fdToWatcher[int32(fd.Fd())] = &e.watchers[n]
   154  	return nil
   155  }
   156  
   157  func (e *Epoll) DeleteWatcher(fileName string) error {
   158  	w, ok := e.fileToWatcher[fileName]
   159  	if !ok {
   160  		return ErrWatcherNotFound
   161  	}
   162  	err := e.deleteFromEpoll(w.fd)
   163  	if err != nil {
   164  		return err
   165  	}
   166  	delete(e.fileToWatcher, fileName)
   167  	delete(e.fdToWatcher, int32(w.fd.Fd()))
   168  	return nil
   169  }
   170  
   171  func (e *Epoll) addToEpoll(fd *os.File, events []EventType) error {
   172  	ifd := int32(fd.Fd())
   173  	var evs EventType
   174  	for _, e := range events {
   175  		evs |= e
   176  	}
   177  	return syscall.EpollCtl(e.epfd, syscall.EPOLL_CTL_ADD, int(ifd),
   178  		&syscall.EpollEvent{
   179  			Events: uint32(evs),
   180  			Fd:     ifd,
   181  		})
   182  }
   183  
   184  func (e *Epoll) deleteFromEpoll(fd *os.File) error {
   185  	return syscall.EpollCtl(e.epfd, syscall.EPOLL_CTL_DEL, int(fd.Fd()), nil)
   186  }
   187  
   188  func (e *Epoll) dispatchEvent(ev *syscall.EpollEvent) {
   189  	w, ok := e.fdToWatcher[ev.Fd]
   190  	// Skip if no watcher found
   191  	if !ok {
   192  		return
   193  	}
   194  	klog.V(4).Infof("epoll event for file %s dispatched", w.FileName)
   195  
   196  	e.eventChan <- Event{
   197  		fileName: w.FileName,
   198  		events:   ev.Events,
   199  	}
   200  }
   201  
   202  func (e *Epoll) listen() {
   203  	events := make([]syscall.EpollEvent, 2)
   204  	for e.active {
   205  		// TODO: Handle errors here
   206  		klog.V(4).Info("waiting for epoll events...")
   207  		count, _ := syscall.EpollWait(e.epfd, events, timeout)
   208  		klog.V(4).Infof("received %d events from epoll. dispatching...", count)
   209  		for i := 0; e.active && i < count; i++ {
   210  			e.dispatchEvent(&events[i])
   211  		}
   212  	}
   213  }
   214  
   215  func (ev *Event) IsType(evt EventType) bool {
   216  	return ev.events&uint32(evt) != 0
   217  }
   218  
   219  func (ev *Event) FileName() string {
   220  	return ev.fileName
   221  }
   222  
   223  func isValidEventType(evt EventType) bool {
   224  	switch evt {
   225  	case EPOLLERR, EPOLLHUP, EPOLLIN, EPOLLOUT, EPOLLPRI:
   226  		return true
   227  	}
   228  
   229  	return false
   230  }