mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
65783256ca
`notifier.addFD()` was simply updating the FD map, assuming that the queue is new and has no events registered. However, this assumption may not hold for some callers of fdnotifier.AddFD(), notably transport.connectionedEndpoint.SetBoundSocketFD(). This situation can arise if the application does socket(2) -> epoll_ctl(EPOLL_CTL_ADD) -> bind(2). This change gets rid of the said assumption. Fixes #9848 PiperOrigin-RevId: 595171718
216 lines
5.1 KiB
Go
216 lines
5.1 KiB
Go
// Copyright 2018 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//go:build linux
|
|
// +build linux
|
|
|
|
// Package fdnotifier contains an adapter that translates IO events (e.g., a
|
|
// file became readable/writable) from native FDs to the notifications in the
|
|
// waiter package. It uses epoll in edge-triggered mode to receive notifications
|
|
// for registered FDs.
|
|
package fdnotifier
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"golang.org/x/sys/unix"
|
|
"gvisor.dev/gvisor/pkg/sync"
|
|
"gvisor.dev/gvisor/pkg/waiter"
|
|
)
|
|
|
|
type fdInfo struct {
|
|
queue *waiter.Queue
|
|
waiting bool
|
|
}
|
|
|
|
// notifier holds all the state necessary to issue notifications when IO events
|
|
// occur in the observed FDs.
|
|
type notifier struct {
|
|
// epFD is the epoll file descriptor used to register for io
|
|
// notifications.
|
|
epFD int
|
|
|
|
// mu protects fdMap.
|
|
mu sync.Mutex
|
|
|
|
// fdMap maps file descriptors to their notification queues and waiting
|
|
// status.
|
|
fdMap map[int32]*fdInfo
|
|
}
|
|
|
|
// newNotifier creates a new notifier object.
|
|
func newNotifier() (*notifier, error) {
|
|
epfd, err := unix.EpollCreate1(0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w := ¬ifier{
|
|
epFD: epfd,
|
|
fdMap: make(map[int32]*fdInfo),
|
|
}
|
|
|
|
go w.waitAndNotify() // S/R-SAFE: no waiter exists during save / load.
|
|
|
|
return w, nil
|
|
}
|
|
|
|
// waitFD waits on mask for fd. The fdMap mutex must be hold.
|
|
func (n *notifier) waitFD(fd int32, fi *fdInfo, mask waiter.EventMask) error {
|
|
if !fi.waiting && mask == 0 {
|
|
return nil
|
|
}
|
|
|
|
e := unix.EpollEvent{
|
|
Events: mask.ToLinux() | unix.EPOLLET,
|
|
Fd: fd,
|
|
}
|
|
|
|
switch {
|
|
case !fi.waiting && mask != 0:
|
|
if err := unix.EpollCtl(n.epFD, unix.EPOLL_CTL_ADD, int(fd), &e); err != nil {
|
|
return err
|
|
}
|
|
fi.waiting = true
|
|
case fi.waiting && mask == 0:
|
|
unix.EpollCtl(n.epFD, unix.EPOLL_CTL_DEL, int(fd), nil)
|
|
fi.waiting = false
|
|
case fi.waiting && mask != 0:
|
|
if err := unix.EpollCtl(n.epFD, unix.EPOLL_CTL_MOD, int(fd), &e); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// addFD adds an FD to the list of FDs observed by n.
|
|
func (n *notifier) addFD(fd int32, queue *waiter.Queue) error {
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
|
|
// Panic if we're already notifying on this FD.
|
|
if _, ok := n.fdMap[fd]; ok {
|
|
panic(fmt.Sprintf("File descriptor %v added twice", fd))
|
|
}
|
|
|
|
info := &fdInfo{queue: queue}
|
|
// We might already have something in queue to wait for.
|
|
if err := n.waitFD(fd, info, queue.Events()); err != nil {
|
|
return err
|
|
}
|
|
// Add it to the map.
|
|
n.fdMap[fd] = info
|
|
return nil
|
|
}
|
|
|
|
// updateFD updates the set of events the fd needs to be notified on.
|
|
func (n *notifier) updateFD(fd int32) error {
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
|
|
if fi, ok := n.fdMap[fd]; ok {
|
|
return n.waitFD(fd, fi, fi.queue.Events())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveFD removes an FD from the list of FDs observed by n.
|
|
func (n *notifier) removeFD(fd int32) {
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
|
|
// Remove from map, then from epoll object.
|
|
n.waitFD(fd, n.fdMap[fd], 0)
|
|
delete(n.fdMap, fd)
|
|
}
|
|
|
|
// hasFD returns true if the fd is in the list of observed FDs.
|
|
func (n *notifier) hasFD(fd int32) bool {
|
|
n.mu.Lock()
|
|
defer n.mu.Unlock()
|
|
|
|
_, ok := n.fdMap[fd]
|
|
return ok
|
|
}
|
|
|
|
// waitAndNotify run is its own goroutine and loops waiting for io event
|
|
// notifications from the epoll object. Once notifications arrive, they are
|
|
// dispatched to the registered queue.
|
|
func (n *notifier) waitAndNotify() error {
|
|
e := make([]unix.EpollEvent, 100)
|
|
for {
|
|
v, err := epollWait(n.epFD, e, -1)
|
|
if err == unix.EINTR {
|
|
continue
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
notified := false
|
|
n.mu.Lock()
|
|
for i := 0; i < v; i++ {
|
|
if fi, ok := n.fdMap[e[i].Fd]; ok {
|
|
fi.queue.Notify(waiter.EventMaskFromLinux(e[i].Events))
|
|
notified = true
|
|
}
|
|
}
|
|
n.mu.Unlock()
|
|
if notified {
|
|
// Let goroutines woken by Notify get a chance to run before we
|
|
// epoll_wait again.
|
|
sync.Goyield()
|
|
}
|
|
}
|
|
}
|
|
|
|
var shared struct {
|
|
notifier *notifier
|
|
once sync.Once
|
|
initErr error
|
|
}
|
|
|
|
// AddFD adds an FD to the list of observed FDs.
|
|
func AddFD(fd int32, queue *waiter.Queue) error {
|
|
shared.once.Do(func() {
|
|
shared.notifier, shared.initErr = newNotifier()
|
|
})
|
|
|
|
if shared.initErr != nil {
|
|
return shared.initErr
|
|
}
|
|
|
|
return shared.notifier.addFD(fd, queue)
|
|
}
|
|
|
|
// UpdateFD updates the set of events the fd needs to be notified on.
|
|
func UpdateFD(fd int32) error {
|
|
return shared.notifier.updateFD(fd)
|
|
}
|
|
|
|
// RemoveFD removes an FD from the list of observed FDs.
|
|
func RemoveFD(fd int32) {
|
|
shared.notifier.removeFD(fd)
|
|
}
|
|
|
|
// HasFD returns true if the FD is in the list of observed FDs.
|
|
//
|
|
// This should only be used by tests to assert that FDs are correctly registered.
|
|
func HasFD(fd int32) bool {
|
|
return shared.notifier.hasFD(fd)
|
|
}
|