Files
Ayush Ranjan 65783256ca Fix fdnotifier.AddFD() to handle the case when queue already has events.
`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
2024-01-02 11:44:45 -08:00

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 := &notifier{
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)
}