whatcanGOwrong

This commit is contained in:
2024-09-19 21:38:24 -04:00
commit d0ae4d841d
17908 changed files with 4096831 additions and 0 deletions
@@ -0,0 +1,225 @@
package epoll
import (
"fmt"
"math"
"os"
"runtime"
"sync"
"time"
"github.com/cilium/ebpf/internal"
"github.com/cilium/ebpf/internal/unix"
)
// Poller waits for readiness notifications from multiple file descriptors.
//
// The wait can be interrupted by calling Close.
type Poller struct {
// mutexes protect the fields declared below them. If you need to
// acquire both at once you must lock epollMu before eventMu.
epollMu sync.Mutex
epollFd int
eventMu sync.Mutex
event *eventFd
}
func New() (*Poller, error) {
epollFd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC)
if err != nil {
return nil, fmt.Errorf("create epoll fd: %v", err)
}
p := &Poller{epollFd: epollFd}
p.event, err = newEventFd()
if err != nil {
unix.Close(epollFd)
return nil, err
}
if err := p.Add(p.event.raw, 0); err != nil {
unix.Close(epollFd)
p.event.close()
return nil, fmt.Errorf("add eventfd: %w", err)
}
runtime.SetFinalizer(p, (*Poller).Close)
return p, nil
}
// Close the poller.
//
// Interrupts any calls to Wait. Multiple calls to Close are valid, but subsequent
// calls will return os.ErrClosed.
func (p *Poller) Close() error {
runtime.SetFinalizer(p, nil)
// Interrupt Wait() via the event fd if it's currently blocked.
if err := p.wakeWait(); err != nil {
return err
}
// Acquire the lock. This ensures that Wait isn't running.
p.epollMu.Lock()
defer p.epollMu.Unlock()
// Prevent other calls to Close().
p.eventMu.Lock()
defer p.eventMu.Unlock()
if p.epollFd != -1 {
unix.Close(p.epollFd)
p.epollFd = -1
}
if p.event != nil {
p.event.close()
p.event = nil
}
return nil
}
// Add an fd to the poller.
//
// id is returned by Wait in the unix.EpollEvent.Pad field any may be zero. It
// must not exceed math.MaxInt32.
//
// Add is blocked by Wait.
func (p *Poller) Add(fd int, id int) error {
if int64(id) > math.MaxInt32 {
return fmt.Errorf("unsupported id: %d", id)
}
p.epollMu.Lock()
defer p.epollMu.Unlock()
if p.epollFd == -1 {
return fmt.Errorf("epoll add: %w", os.ErrClosed)
}
// The representation of EpollEvent isn't entirely accurate.
// Pad is fully useable, not just padding. Hence we stuff the
// id in there, which allows us to identify the event later (e.g.,
// in case of perf events, which CPU sent it).
event := unix.EpollEvent{
Events: unix.EPOLLIN,
Fd: int32(fd),
Pad: int32(id),
}
if err := unix.EpollCtl(p.epollFd, unix.EPOLL_CTL_ADD, fd, &event); err != nil {
return fmt.Errorf("add fd to epoll: %v", err)
}
return nil
}
// Wait for events.
//
// Returns the number of pending events or an error wrapping os.ErrClosed if
// Close is called, or os.ErrDeadlineExceeded if EpollWait timeout.
func (p *Poller) Wait(events []unix.EpollEvent, deadline time.Time) (int, error) {
p.epollMu.Lock()
defer p.epollMu.Unlock()
if p.epollFd == -1 {
return 0, fmt.Errorf("epoll wait: %w", os.ErrClosed)
}
for {
timeout := int(-1)
if !deadline.IsZero() {
msec := time.Until(deadline).Milliseconds()
if msec < 0 {
// Deadline is in the past.
msec = 0
} else if msec > math.MaxInt {
// Deadline is too far in the future.
msec = math.MaxInt
}
timeout = int(msec)
}
n, err := unix.EpollWait(p.epollFd, events, timeout)
if temp, ok := err.(temporaryError); ok && temp.Temporary() {
// Retry the syscall if we were interrupted, see https://github.com/golang/go/issues/20400
continue
}
if err != nil {
return 0, err
}
if n == 0 {
return 0, fmt.Errorf("epoll wait: %w", os.ErrDeadlineExceeded)
}
for _, event := range events[:n] {
if int(event.Fd) == p.event.raw {
// Since we don't read p.event the event is never cleared and
// we'll keep getting this wakeup until Close() acquires the
// lock and sets p.epollFd = -1.
return 0, fmt.Errorf("epoll wait: %w", os.ErrClosed)
}
}
return n, nil
}
}
type temporaryError interface {
Temporary() bool
}
// wakeWait unblocks Wait if it's epoll_wait.
func (p *Poller) wakeWait() error {
p.eventMu.Lock()
defer p.eventMu.Unlock()
if p.event == nil {
return fmt.Errorf("epoll wake: %w", os.ErrClosed)
}
return p.event.add(1)
}
// eventFd wraps a Linux eventfd.
//
// An eventfd acts like a counter: writes add to the counter, reads retrieve
// the counter and reset it to zero. Reads also block if the counter is zero.
//
// See man 2 eventfd.
type eventFd struct {
file *os.File
// prefer raw over file.Fd(), since the latter puts the file into blocking
// mode.
raw int
}
func newEventFd() (*eventFd, error) {
fd, err := unix.Eventfd(0, unix.O_CLOEXEC|unix.O_NONBLOCK)
if err != nil {
return nil, err
}
file := os.NewFile(uintptr(fd), "event")
return &eventFd{file, fd}, nil
}
func (efd *eventFd) close() error {
return efd.file.Close()
}
func (efd *eventFd) add(n uint64) error {
var buf [8]byte
internal.NativeEndian.PutUint64(buf[:], 1)
_, err := efd.file.Write(buf[:])
return err
}
func (efd *eventFd) read() (uint64, error) {
var buf [8]byte
_, err := efd.file.Read(buf[:])
return internal.NativeEndian.Uint64(buf[:]), err
}
@@ -0,0 +1,130 @@
package epoll
import (
"errors"
"math"
"os"
"testing"
"time"
"github.com/cilium/ebpf/internal/unix"
)
func TestPoller(t *testing.T) {
t.Parallel()
event, poller := mustNewPoller(t)
done := make(chan struct{}, 1)
read := func() {
defer func() {
done <- struct{}{}
}()
events := make([]unix.EpollEvent, 1)
n, err := poller.Wait(events, time.Time{})
if errors.Is(err, os.ErrClosed) {
return
}
if err != nil {
t.Error("Error from wait:", err)
return
}
if n != 1 {
t.Errorf("Got %d instead of 1 events", n)
}
if e := events[0]; e.Pad != 42 {
t.Errorf("Incorrect value in EpollEvent.Pad: %d != 42", e.Pad)
}
}
if err := event.add(1); err != nil {
t.Fatal(err)
}
go read()
select {
case <-done:
case <-time.After(time.Second):
t.Fatal("Timed out")
}
if _, err := event.read(); err != nil {
t.Fatal(err)
}
go read()
select {
case <-done:
t.Fatal("Wait doesn't block")
case <-time.After(time.Second):
}
if err := poller.Close(); err != nil {
t.Fatal("Close returns an error:", err)
}
select {
case <-done:
case <-time.After(time.Second):
t.Fatal("Close doesn't unblock Wait")
}
if err := poller.Close(); !errors.Is(err, os.ErrClosed) {
t.Fatal("Closing a second time doesn't return ErrClosed:", err)
}
}
func TestPollerDeadline(t *testing.T) {
t.Parallel()
_, poller := mustNewPoller(t)
events := make([]unix.EpollEvent, 1)
_, err := poller.Wait(events, time.Now().Add(-time.Second))
if !errors.Is(err, os.ErrDeadlineExceeded) {
t.Fatal("Expected os.ErrDeadlineExceeded on deadline in the past, got", err)
}
done := make(chan struct{})
go func() {
defer close(done)
_, err := poller.Wait(events, time.Now().Add(math.MaxInt64))
if !errors.Is(err, os.ErrClosed) {
t.Error("Expected os.ErrClosed when interrupting deadline, got", err)
}
}()
// Wait for the goroutine to enter the syscall.
time.Sleep(time.Second)
poller.Close()
<-done
}
func mustNewPoller(t *testing.T) (*eventFd, *Poller) {
t.Helper()
event, err := newEventFd()
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { event.close() })
poller, err := New()
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { poller.Close() })
if err := poller.Add(event.raw, 42); err != nil {
t.Fatal("Can't add fd:", err)
}
return event, poller
}