1package fdwait
2
3import (
4 "context"
5 "errors"
6 "testing"
7 "time"
8
9 "golang.org/x/sys/unix"
10)
11
12// A pinned fd is reported like a transient one, costs no epoll call per
13// Poll, and a ready pinned fd a Poll does not list fails it instead of
14// spinning.
15func TestPollPinned(t *testing.T) {
16 p, err := New()
17 if err != nil {
18 t.Fatal(err)
19 }
20 defer p.Close()
21 pinned, other := eventfd(t), eventfd(t)
22 if err := p.Pin(int32(pinned)); err != nil {
23 t.Fatal(err)
24 }
25 fds := []unix.PollFd{{Fd: int32(pinned), Events: unix.POLLIN}}
26 if n, err := p.Poll(fds, 1); n != 0 || err != nil {
27 t.Fatalf("idle: %d %v", n, err)
28 }
29 go unix.Write(pinned, []byte{1, 0, 0, 0, 0, 0, 0, 0})
30 if n, err := p.Poll(fds, 1000); n != 1 || err != nil || fds[0].Revents&unix.POLLIN == 0 {
31 t.Fatalf("pinned ready: %d %v %+v", n, err, fds)
32 }
33 // Still registered after Poll returns: it is pinned.
34 if err := p.raw.Control(func(ep uintptr) {
35 var events [2]unix.EpollEvent
36 if n, err := unix.EpollWait(int(ep), events[:], 0); n != 1 || err != nil || events[0].Fd != int32(pinned) {
37 t.Errorf("pinned registration: %d %v %+v", n, err, events[:n])
38 }
39 }); err != nil {
40 t.Fatal(err)
41 }
42 if _, err := p.Poll([]unix.PollFd{{Fd: int32(other), Events: unix.POLLIN}}, 1000); err == nil {
43 t.Fatal("Poll without a ready pinned fd succeeded")
44 }
45}
46
47// Polling only pinned fds allocates nothing and makes no epoll_ctl call.
48func TestPollPinnedAllocations(t *testing.T) {
49 p, err := New()
50 if err != nil {
51 t.Fatal(err)
52 }
53 defer p.Close()
54 fd := eventfd(t)
55 if err := p.Pin(int32(fd)); err != nil {
56 t.Fatal(err)
57 }
58 fds := []unix.PollFd{{Fd: int32(fd), Events: unix.POLLIN}}
59 signal(t, fd)
60 p.Poll(fds, -1)
61 if allocs := testing.AllocsPerRun(100, func() { p.Poll(fds, -1) }); allocs != 0 {
62 t.Errorf("pinned ready Poll: %.1f allocs, want 0", allocs)
63 }
64}
65
66// Wake ends a wait without a timeout and returns no events; wakes before a
67// Poll coalesce into one early return.
68func TestPollWake(t *testing.T) {
69 p, err := New()
70 if err != nil {
71 t.Fatal(err)
72 }
73 defer p.Close()
74 fds := []unix.PollFd{{Fd: int32(eventfd(t)), Events: unix.POLLIN}}
75 done := make(chan error, 1)
76 go func() {
77 n, err := p.Poll(fds, -1)
78 if err == nil && n != 0 {
79 err = errors.New("woken poll reported events")
80 }
81 done <- err
82 }()
83 p.Wake()
84 select {
85 case err := <-done:
86 if err != nil {
87 t.Fatal(err)
88 }
89 case <-time.After(5 * time.Second):
90 t.Fatal("Wake did not end the wait")
91 }
92 p.Wake()
93 p.Wake()
94 if n, err := p.Poll(fds, 1000); n != 0 || err != nil {
95 t.Fatalf("coalesced wake: %d %v", n, err)
96 }
97 if n, err := p.Poll(fds, 1); n != 0 || err != nil {
98 t.Fatalf("second wake was not coalesced: %d %v", n, err)
99 }
100}
101
102// After the StopOn context ends, a running and every later Poll return at
103// once, also with no timeout.
104func TestPollStopOn(t *testing.T) {
105 p, err := New()
106 if err != nil {
107 t.Fatal(err)
108 }
109 defer p.Close()
110 ctx, cancel := context.WithCancel(context.Background())
111 p.StopOn(ctx)
112 fds := []unix.PollFd{{Fd: int32(eventfd(t)), Events: unix.POLLIN}}
113 done := make(chan error, 1)
114 go func() { _, err := p.Poll(fds, -1); done <- err }()
115 cancel()
116 select {
117 case err := <-done:
118 if err != nil {
119 t.Fatal(err)
120 }
121 case <-time.After(5 * time.Second):
122 t.Fatal("cancel did not end the wait")
123 }
124 for range 3 {
125 if n, err := p.Poll(fds, -1); n != 0 || err != nil {
126 t.Fatalf("poll after stop: %d %v", n, err)
127 }
128 }
129}
130
131func eventfd(t *testing.T) int {
132 t.Helper()
133 fd, err := unix.Eventfd(0, unix.EFD_NONBLOCK|unix.EFD_CLOEXEC)
134 if err != nil {
135 t.Fatal(err)
136 }
137 t.Cleanup(func() { unix.Close(fd) })
138 return fd
139}
140
141func signal(t *testing.T, fd int) {
142 t.Helper()
143 if _, err := unix.Write(fd, []byte{1, 0, 0, 0, 0, 0, 0, 0}); err != nil {
144 t.Fatal(err)
145 }
146}
147
148// assertNoWatches fails if Poll left a registration behind, by making every
149// test descriptor readable and checking epoll reports nothing.
150func assertNoWatches(t *testing.T, p *Poller, fds ...int) {
151 t.Helper()
152 for _, fd := range fds {
153 signal(t, fd)
154 }
155 if err := p.raw.Control(func(ep uintptr) {
156 var events [4]unix.EpollEvent
157 if n, err := unix.EpollWait(int(ep), events[:], 0); n != 0 || err != nil {
158 t.Errorf("registrations left after Poll returned: %d %v", n, err)
159 }
160 }); err != nil {
161 t.Fatal(err)
162 }
163}
164
165// Poll(fds, 0) reports existing readiness like poll(2): an expired netpoll
166// deadline never runs the readiness check.
167func TestPollZeroTimeoutReportsReady(t *testing.T) {
168 p, err := New()
169 if err != nil {
170 t.Fatal(err)
171 }
172 defer p.Close()
173 fd := eventfd(t)
174 fds := []unix.PollFd{{Fd: -1}, {Fd: int32(fd), Events: unix.POLLIN}}
175 if n, err := p.Poll(fds, 0); n != 0 || err != nil {
176 t.Fatalf("idle: %d %v", n, err)
177 }
178 signal(t, fd)
179 if n, err := p.Poll(fds, 0); n != 1 || err != nil || fds[1].Revents&unix.POLLIN == 0 {
180 t.Fatalf("ready: %d %v %+v", n, err, fds)
181 }
182}
183
184// A descriptor listed twice is watched once and removed once.
185func TestPollDuplicateDescriptor(t *testing.T) {
186 p, err := New()
187 if err != nil {
188 t.Fatal(err)
189 }
190 defer p.Close()
191 fd := eventfd(t)
192 fds := []unix.PollFd{{Fd: int32(fd), Events: unix.POLLIN}, {Fd: int32(fd), Events: unix.POLLIN}}
193 if n, err := p.Poll(fds, 1); n != 0 || err != nil {
194 t.Fatalf("duplicate: %d %v", n, err)
195 }
196 assertNoWatches(t, p, fd)
197}
198
199// A failed registration removes the ones already added in the same call.
200func TestPollFailedWatchRemovesAdded(t *testing.T) {
201 p, err := New()
202 if err != nil {
203 t.Fatal(err)
204 }
205 defer p.Close()
206 fd := eventfd(t)
207 var self int32
208 if err := p.raw.Control(func(ep uintptr) { self = int32(ep) }); err != nil {
209 t.Fatal(err)
210 }
211 // epoll refuses to watch itself (EINVAL) and is not ready.
212 fds := []unix.PollFd{{Fd: int32(fd), Events: unix.POLLIN}, {Fd: self, Events: unix.POLLIN}}
213 if _, err := p.Poll(fds, 1); err == nil {
214 t.Fatal("watching the epoll fd itself succeeded")
215 }
216 assertNoWatches(t, p, fd)
217}
218
219// A registration whose fd was closed while another reference keeps its file
220// open cannot be removed: Poll fails from then on instead of spinning.
221func TestPollFailsAfterLostRegistration(t *testing.T) {
222 p, err := New()
223 if err != nil {
224 t.Fatal(err)
225 }
226 defer p.Close()
227 fd, err := unix.Eventfd(0, unix.EFD_NONBLOCK|unix.EFD_CLOEXEC)
228 if err != nil {
229 t.Fatal(err)
230 }
231 dup, err := unix.Dup(fd)
232 if err != nil {
233 unix.Close(fd)
234 t.Fatal(err)
235 }
236 defer unix.Close(dup)
237 if err := p.raw.Control(func(ep uintptr) {
238 ev := unix.EpollEvent{Events: unix.EPOLLIN, Fd: int32(fd)}
239 if err := unix.EpollCtl(int(ep), unix.EPOLL_CTL_ADD, fd, &ev); err != nil {
240 t.Fatal(err)
241 }
242 }); err != nil {
243 t.Fatal(err)
244 }
245 unix.Close(fd) // what a caller closing fd during a wait does
246 p.fds, p.added = []unix.PollFd{{Fd: int32(fd), Events: unix.POLLIN}}, 1
247 p.unwatch()
248 p.fds = nil
249 other := eventfd(t)
250 if _, err := p.Poll([]unix.PollFd{{Fd: int32(other), Events: unix.POLLIN}}, 1); err == nil {
251 t.Fatal("Poll succeeded with a registration it could not remove")
252 }
253}