v0.10.0 d5d679d

edges_test.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 23

Tags 10

↑↓ moveenter openesc close