1// Package fdwait parks descriptor waits in Go's netpoller.
2package fdwait
3
4import (
5 "errors"
6 "os"
7 "syscall"
8 "time"
9
10 "golang.org/x/sys/unix"
11)
12
13// Poller owns an epoll descriptor and is used by one goroutine.
14// Watched descriptors remain owned by the caller.
15type Poller struct {
16 file *os.File
17 raw syscall.RawConn
18}
19
20func New() (*Poller, error) {
21 fd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC)
22 if err != nil {
23 return nil, err
24 }
25 if err = unix.SetNonblock(fd, true); err != nil {
26 unix.Close(fd)
27 return nil, err
28 }
29 f := os.NewFile(uintptr(fd), "fdwait")
30 raw, err := f.SyscallConn()
31 if err != nil {
32 f.Close()
33 return nil, err
34 }
35 return &Poller{file: f, raw: raw}, nil
36}
37func (p *Poller) Close() error { return p.file.Close() }
38
39// Poll waits for readable/error events. It never consumes caller events.
40// Each call explicitly removes its epoll registrations before the caller can
41// close or recycle watched fds. Relying on close to remove them is unsafe:
42// another fd or a kernel fence may retain the open file description.
43func (p *Poller) Poll(fds []unix.PollFd, timeout int) (int, error) {
44 var ctlErr error
45 added := 0
46 err := p.raw.Control(func(ep uintptr) {
47 for _, f := range fds {
48 if f.Fd < 0 {
49 added++
50 continue
51 }
52 ev := unix.EpollEvent{Events: unix.EPOLLIN, Fd: f.Fd}
53 if e := unix.EpollCtl(int(ep), unix.EPOLL_CTL_ADD, int(f.Fd), &ev); e != nil {
54 ctlErr = e
55 return
56 }
57 added++
58 }
59 })
60 defer p.raw.Control(func(ep uintptr) {
61 for _, f := range fds[:added] {
62 if f.Fd >= 0 {
63 unix.EpollCtl(int(ep), unix.EPOLL_CTL_DEL, int(f.Fd), nil)
64 }
65 }
66 })
67 if err != nil {
68 return 0, err
69 }
70 if ctlErr != nil {
71 return 0, ctlErr
72 }
73 if timeout >= 0 {
74 err = p.file.SetReadDeadline(time.Now().Add(time.Duration(timeout) * time.Millisecond))
75 } else {
76 err = p.file.SetReadDeadline(time.Time{})
77 }
78 if err != nil {
79 return 0, err
80 }
81 var n int
82 var pollErr error
83 err = p.raw.Read(func(_ uintptr) bool {
84 n, pollErr = unix.Poll(fds, 0)
85 return n > 0 || (pollErr != nil && !errors.Is(pollErr, unix.EINTR))
86 })
87 if errors.Is(err, os.ErrDeadlineExceeded) {
88 return 0, nil
89 }
90 if err != nil {
91 return 0, err
92 }
93 return n, pollErr
94}