github.com/sagernet/gvisor@v0.0.0-20240428053021-e691de28565f/pkg/fdnotifier/fdnotifier.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build linux
    16  // +build linux
    17  
    18  // Package fdnotifier contains an adapter that translates IO events (e.g., a
    19  // file became readable/writable) from native FDs to the notifications in the
    20  // waiter package. It uses epoll in edge-triggered mode to receive notifications
    21  // for registered FDs.
    22  package fdnotifier
    23  
    24  import (
    25  	"fmt"
    26  
    27  	"golang.org/x/sys/unix"
    28  	"github.com/sagernet/gvisor/pkg/sync"
    29  	"github.com/sagernet/gvisor/pkg/waiter"
    30  )
    31  
    32  type fdInfo struct {
    33  	queue   *waiter.Queue
    34  	waiting bool
    35  }
    36  
    37  // notifier holds all the state necessary to issue notifications when IO events
    38  // occur in the observed FDs.
    39  type notifier struct {
    40  	// epFD is the epoll file descriptor used to register for io
    41  	// notifications.
    42  	epFD int
    43  
    44  	// mu protects fdMap.
    45  	mu sync.Mutex
    46  
    47  	// fdMap maps file descriptors to their notification queues and waiting
    48  	// status.
    49  	fdMap map[int32]*fdInfo
    50  }
    51  
    52  // newNotifier creates a new notifier object.
    53  func newNotifier() (*notifier, error) {
    54  	epfd, err := unix.EpollCreate1(0)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	w := &notifier{
    60  		epFD:  epfd,
    61  		fdMap: make(map[int32]*fdInfo),
    62  	}
    63  
    64  	go w.waitAndNotify() // S/R-SAFE: no waiter exists during save / load.
    65  
    66  	return w, nil
    67  }
    68  
    69  // waitFD waits on mask for fd. The fdMap mutex must be hold.
    70  func (n *notifier) waitFD(fd int32, fi *fdInfo, mask waiter.EventMask) error {
    71  	if !fi.waiting && mask == 0 {
    72  		return nil
    73  	}
    74  
    75  	e := unix.EpollEvent{
    76  		Events: mask.ToLinux() | unix.EPOLLET,
    77  		Fd:     fd,
    78  	}
    79  
    80  	switch {
    81  	case !fi.waiting && mask != 0:
    82  		if err := unix.EpollCtl(n.epFD, unix.EPOLL_CTL_ADD, int(fd), &e); err != nil {
    83  			return err
    84  		}
    85  		fi.waiting = true
    86  	case fi.waiting && mask == 0:
    87  		unix.EpollCtl(n.epFD, unix.EPOLL_CTL_DEL, int(fd), nil)
    88  		fi.waiting = false
    89  	case fi.waiting && mask != 0:
    90  		if err := unix.EpollCtl(n.epFD, unix.EPOLL_CTL_MOD, int(fd), &e); err != nil {
    91  			return err
    92  		}
    93  	}
    94  
    95  	return nil
    96  }
    97  
    98  // addFD adds an FD to the list of FDs observed by n.
    99  func (n *notifier) addFD(fd int32, queue *waiter.Queue) error {
   100  	n.mu.Lock()
   101  	defer n.mu.Unlock()
   102  
   103  	// Panic if we're already notifying on this FD.
   104  	if _, ok := n.fdMap[fd]; ok {
   105  		panic(fmt.Sprintf("File descriptor %v added twice", fd))
   106  	}
   107  
   108  	info := &fdInfo{queue: queue}
   109  	// We might already have something in queue to wait for.
   110  	if err := n.waitFD(fd, info, queue.Events()); err != nil {
   111  		return err
   112  	}
   113  	// Add it to the map.
   114  	n.fdMap[fd] = info
   115  	return nil
   116  }
   117  
   118  // updateFD updates the set of events the fd needs to be notified on.
   119  func (n *notifier) updateFD(fd int32) error {
   120  	n.mu.Lock()
   121  	defer n.mu.Unlock()
   122  
   123  	if fi, ok := n.fdMap[fd]; ok {
   124  		return n.waitFD(fd, fi, fi.queue.Events())
   125  	}
   126  
   127  	return nil
   128  }
   129  
   130  // RemoveFD removes an FD from the list of FDs observed by n.
   131  func (n *notifier) removeFD(fd int32) {
   132  	n.mu.Lock()
   133  	defer n.mu.Unlock()
   134  
   135  	// Remove from map, then from epoll object.
   136  	n.waitFD(fd, n.fdMap[fd], 0)
   137  	delete(n.fdMap, fd)
   138  }
   139  
   140  // hasFD returns true if the fd is in the list of observed FDs.
   141  func (n *notifier) hasFD(fd int32) bool {
   142  	n.mu.Lock()
   143  	defer n.mu.Unlock()
   144  
   145  	_, ok := n.fdMap[fd]
   146  	return ok
   147  }
   148  
   149  // waitAndNotify run is its own goroutine and loops waiting for io event
   150  // notifications from the epoll object. Once notifications arrive, they are
   151  // dispatched to the registered queue.
   152  func (n *notifier) waitAndNotify() error {
   153  	e := make([]unix.EpollEvent, 100)
   154  	for {
   155  		v, err := epollWait(n.epFD, e, -1)
   156  		if err == unix.EINTR {
   157  			continue
   158  		}
   159  
   160  		if err != nil {
   161  			return err
   162  		}
   163  
   164  		notified := false
   165  		n.mu.Lock()
   166  		for i := 0; i < v; i++ {
   167  			if fi, ok := n.fdMap[e[i].Fd]; ok {
   168  				fi.queue.Notify(waiter.EventMaskFromLinux(e[i].Events))
   169  				notified = true
   170  			}
   171  		}
   172  		n.mu.Unlock()
   173  		if notified {
   174  			// Let goroutines woken by Notify get a chance to run before we
   175  			// epoll_wait again.
   176  			sync.Goyield()
   177  		}
   178  	}
   179  }
   180  
   181  var shared struct {
   182  	notifier *notifier
   183  	once     sync.Once
   184  	initErr  error
   185  }
   186  
   187  // AddFD adds an FD to the list of observed FDs.
   188  func AddFD(fd int32, queue *waiter.Queue) error {
   189  	shared.once.Do(func() {
   190  		shared.notifier, shared.initErr = newNotifier()
   191  	})
   192  
   193  	if shared.initErr != nil {
   194  		return shared.initErr
   195  	}
   196  
   197  	return shared.notifier.addFD(fd, queue)
   198  }
   199  
   200  // UpdateFD updates the set of events the fd needs to be notified on.
   201  func UpdateFD(fd int32) error {
   202  	return shared.notifier.updateFD(fd)
   203  }
   204  
   205  // RemoveFD removes an FD from the list of observed FDs.
   206  func RemoveFD(fd int32) {
   207  	shared.notifier.removeFD(fd)
   208  }
   209  
   210  // HasFD returns true if the FD is in the list of observed FDs.
   211  //
   212  // This should only be used by tests to assert that FDs are correctly registered.
   213  func HasFD(fd int32) bool {
   214  	return shared.notifier.hasFD(fd)
   215  }