summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-10-26 19:05:22 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-10-26 19:05:22 +0100
commitf2f5b0450d4b7269bafdfb1bd1788303f6d8ce7b (patch)
tree346678fd8194ed036b2ee98d7b8e1042d5beac06
parent78c3cebc389a2b0d426d74900a65b8656edb426d (diff)
Add poll/epoll/kqueue wrapper to wait for events
-rw-r--r--Makefile8
-rw-r--r--ot_event.c332
-rw-r--r--ot_event.h39
-rw-r--r--tests/runner.c1
-rw-r--r--tests/t_event.c222
-rw-r--r--tests/test.h1
6 files changed, 599 insertions, 4 deletions
diff --git a/Makefile b/Makefile
index d9b8690..a373633 100644
--- a/Makefile
+++ b/Makefile
@@ -68,8 +68,8 @@ CFLAGS+=-I$(LIBOWFAT_HEADERS) -DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread
68LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread 68LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread
69 69
70BINARY =opentracker 70BINARY =opentracker
71HEADERS=trackerlogic.h ot_urlencoded.h ot_batch.h ot_buf.h ot_fdstate.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h 71HEADERS=trackerlogic.h ot_urlencoded.h ot_batch.h ot_buf.h ot_event.h ot_fdstate.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h
72SOURCES=opentracker.c trackerlogic.c ot_urlencoded.c ot_batch.c ot_buf.c ot_fdstate.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c 72SOURCES=opentracker.c trackerlogic.c ot_urlencoded.c ot_batch.c ot_buf.c ot_event.c ot_fdstate.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c
73SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c 73SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c
74 74
75OBJECTS = $(SOURCES:%.c=%.o) 75OBJECTS = $(SOURCES:%.c=%.o)
@@ -119,8 +119,8 @@ $(OBJECTS_proxy_debug): $(HEADERS)
119# has to. tests/stubs.c supplies the few symbols those modules reference but 119# has to. tests/stubs.c supplies the few symbols those modules reference but
120# the tests never reach. 120# the tests never reach.
121TESTBIN=tests/runner 121TESTBIN=tests/runner
122TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_urlencoded.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c tests/t_batch.c tests/t_fdstate.c 122TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_urlencoded.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c tests/t_batch.c tests/t_fdstate.c tests/t_event.c
123TEST_UNITS=ot_urlencoded.c ot_batch.c ot_fdstate.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c 123TEST_UNITS=ot_urlencoded.c ot_event.c ot_batch.c ot_fdstate.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c
124TEST_HEADERS=tests/test.h $(HEADERS) 124TEST_HEADERS=tests/test.h $(HEADERS)
125# Tests reach code that sits behind feature flags, so the units under test are 125# Tests reach code that sits behind feature flags, so the units under test are
126# built with those features enabled. A test binary is not a production binary. 126# built with those features enabled. A test binary is not a production binary.
diff --git a/ot_event.c b/ot_event.c
new file mode 100644
index 0000000..d24227d
--- /dev/null
+++ b/ot_event.c
@@ -0,0 +1,332 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <errno.h>
8#include <stdlib.h>
9#include <string.h>
10#include <unistd.h>
11
12/* Opentracker */
13#include "ot_event.h"
14
15/* One caller waits, so the scratch the mechanism needs is kept here rather
16 than allocated per call. */
17
18/* poll works everywhere and is what the BSDs get until kqueue is written.
19 Defining OT_EVENT_FORCE_POLL picks it on a platform that would otherwise
20 use something better, which is how both are exercised by the tests. */
21#if defined(OT_EVENT_FORCE_POLL)
22#define OT_EVENT_POLL
23#elif defined(OT_EVENT_FORCE_KQUEUE)
24#define OT_EVENT_KQUEUE
25#elif defined(__linux__)
26#define OT_EVENT_EPOLL
27#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__)
28#define OT_EVENT_KQUEUE
29#else
30#define OT_EVENT_POLL
31#endif
32
33#ifdef OT_EVENT_EPOLL
34/* ------------------------------------------------------------- epoll */
35
36#include <sys/epoll.h>
37
38static int g_epoll = -1;
39static struct epoll_event *g_ready;
40static int g_ready_space;
41
42const char *event_mechanism(void) { return "epoll"; }
43
44int event_init(void) {
45 g_ready = NULL;
46 g_ready_space = 0;
47 return (g_epoll = epoll_create1(EPOLL_CLOEXEC)) == -1 ? -1 : 0;
48}
49
50void event_deinit(void) {
51 if (g_epoll != -1)
52 close(g_epoll);
53 free(g_ready);
54 g_epoll = -1;
55 g_ready = NULL;
56 g_ready_space = 0;
57}
58
59static int event_ctl(int op, int fd, int what, uint32_t generation) {
60 struct epoll_event ev;
61
62 memset(&ev, 0, sizeof(ev));
63 ev.events = (what & EVENT_READ ? (uint32_t)EPOLLIN : 0) | (what & EVENT_WRITE ? (uint32_t)EPOLLOUT : 0);
64 /* The generation rides along inside the kernel, so an event says which
65 connection it belongs to without anybody looking it up */
66 ev.data.u64 = ((uint64_t)generation << 32) | (uint32_t)fd;
67 return epoll_ctl(g_epoll, op, fd, &ev);
68}
69
70int event_add(int fd, int what, uint32_t generation) { return event_ctl(EPOLL_CTL_ADD, fd, what, generation); }
71int event_modify(int fd, int what, uint32_t generation) { return event_ctl(EPOLL_CTL_MOD, fd, what, generation); }
72int event_del(int fd) { return epoll_ctl(g_epoll, EPOLL_CTL_DEL, fd, NULL); }
73
74int event_wait(ot_event *out, int max, int timeout_ms) {
75 int count, i;
76
77 if (max > g_ready_space) {
78 struct epoll_event *grown = realloc(g_ready, (size_t)max * sizeof(struct epoll_event));
79
80 if (!grown)
81 return -1;
82 g_ready = grown;
83 g_ready_space = max;
84 }
85
86 while ((count = epoll_wait(g_epoll, g_ready, max, timeout_ms)) == -1 && errno == EINTR)
87 ;
88 for (i = 0; i < count; ++i) {
89 uint32_t events = g_ready[i].events;
90
91 out[i].fd = (int)(uint32_t)g_ready[i].data.u64;
92 out[i].generation = (uint32_t)(g_ready[i].data.u64 >> 32);
93 /* A hangup or an error is reported as both, so whichever side the caller
94 was waiting on gets to notice */
95 out[i].what = (events & (EPOLLIN | EPOLLHUP | EPOLLERR) ? EVENT_READ : 0) |
96 (events & (EPOLLOUT | EPOLLHUP | EPOLLERR) ? EVENT_WRITE : 0);
97 }
98 return count;
99}
100
101#endif /* OT_EVENT_EPOLL */
102
103#ifdef OT_EVENT_KQUEUE
104/* ------------------------------------------------------------ kqueue */
105
106#include <sys/event.h>
107#include <sys/time.h>
108#include <sys/types.h>
109
110static int g_kqueue = -1;
111static struct kevent *g_ready;
112static int g_ready_space;
113
114const char *event_mechanism(void) { return "kqueue"; }
115
116int event_init(void) {
117 g_ready = NULL;
118 g_ready_space = 0;
119 return (g_kqueue = kqueue()) == -1 ? -1 : 0;
120}
121
122void event_deinit(void) {
123 if (g_kqueue != -1)
124 close(g_kqueue);
125 free(g_ready);
126 g_kqueue = -1;
127 g_ready = NULL;
128 g_ready_space = 0;
129}
130
131/* kqueue keeps one registration per filter, so reading and writing are two
132 entries. A change of interest is an add of the one wanted and a delete
133 of the one not. The generation travels in udata, which the kernel passes
134 back untouched. */
135static int event_change(int fd, int what, uint32_t generation, int deleting) {
136 struct kevent change[2], result[2];
137 void *udata = (void *)(uintptr_t)(((uint64_t)generation << 32) | (uint32_t)fd);
138 int succeeded = 0, count, i;
139
140 EV_SET(&change[0], (uintptr_t)fd, EVFILT_READ,
141 EV_RECEIPT | ((deleting || !(what & EVENT_READ)) ? EV_DELETE : EV_ADD), 0, 0, udata);
142 EV_SET(&change[1], (uintptr_t)fd, EVFILT_WRITE,
143 EV_RECEIPT | ((deleting || !(what & EVENT_WRITE)) ? EV_DELETE : EV_ADD), 0, 0, udata);
144
145 if ((count = kevent(g_kqueue, change, 2, result, 2, NULL)) == -1)
146 return -1;
147
148 for (i = 0; i < count && i < 2; ++i) {
149 if (!result[i].data) {
150 ++succeeded;
151 continue;
152 }
153 /* Dropping interest we never registered is not a failure */
154 if ((change[i].flags & EV_DELETE) && result[i].data == ENOENT)
155 continue;
156 errno = (int)result[i].data;
157 return -1;
158 }
159
160 /* Nothing at all was registered or removed, so the descriptor is unknown */
161 if (!succeeded) {
162 errno = ENOENT;
163 return -1;
164 }
165 return 0;
166}
167
168int event_add(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); }
169int event_modify(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); }
170int event_del(int fd) { return event_change(fd, 0, 0, 1); }
171
172int event_wait(ot_event *out, int max, int timeout_ms) {
173 struct timespec timeout;
174 struct timespec *deadline = NULL;
175 int count, i;
176
177 if (max > g_ready_space) {
178 struct kevent *grown = realloc(g_ready, (size_t)max * sizeof(struct kevent));
179
180 if (!grown)
181 return -1;
182 g_ready = grown;
183 g_ready_space = max;
184 }
185
186 if (timeout_ms >= 0) {
187 timeout.tv_sec = timeout_ms / 1000;
188 timeout.tv_nsec = (long)(timeout_ms % 1000) * 1000000;
189 deadline = &timeout;
190 }
191
192 while ((count = kevent(g_kqueue, NULL, 0, g_ready, max, deadline)) == -1 && errno == EINTR)
193 ;
194 for (i = 0; i < count; ++i) {
195 uint64_t udata = (uint64_t)(uintptr_t)g_ready[i].udata;
196 int eof = (g_ready[i].flags & (EV_EOF | EV_ERROR)) != 0;
197
198 out[i].fd = (int)(uint32_t)udata;
199 out[i].generation = (uint32_t)(udata >> 32);
200 out[i].what = ((g_ready[i].filter == EVFILT_READ || eof) ? EVENT_READ : 0) |
201 ((g_ready[i].filter == EVFILT_WRITE || eof) ? EVENT_WRITE : 0);
202 }
203 return count;
204}
205
206#endif /* OT_EVENT_KQUEUE */
207
208#ifdef OT_EVENT_POLL
209/* -------------------------------------------------------------- poll */
210
211#include <poll.h>
212
213/* poll wants its descriptors packed, so the registered ones live in one
214 array and a second maps a descriptor number to its place in it. */
215static struct pollfd *g_pfds;
216static uint32_t *g_generation;
217static int *g_where;
218static int g_count, g_space, g_where_space;
219
220const char *event_mechanism(void) { return "poll"; }
221
222int event_init(void) {
223 g_pfds = NULL, g_generation = NULL, g_where = NULL;
224 g_count = g_space = g_where_space = 0;
225 return 0;
226}
227
228void event_deinit(void) {
229 free(g_pfds), free(g_generation), free(g_where);
230 g_pfds = NULL, g_generation = NULL, g_where = NULL;
231 g_count = g_space = g_where_space = 0;
232}
233
234static short event_to_poll(int what) {
235 return (short)((what & EVENT_READ ? POLLIN : 0) | (what & EVENT_WRITE ? POLLOUT : 0));
236}
237
238static int event_room_for(int fd) {
239 if (fd >= g_where_space) {
240 int space = g_where_space ? g_where_space : 64;
241 int *grown;
242
243 while (space <= fd)
244 space *= 2;
245 if (!(grown = realloc(g_where, (size_t)space * sizeof(int))))
246 return -1;
247 while (g_where_space < space)
248 grown[g_where_space++] = -1;
249 g_where = grown;
250 }
251
252 if (g_count == g_space) {
253 int space = g_space ? g_space * 2 : 64;
254 struct pollfd *pfds = realloc(g_pfds, (size_t)space * sizeof(struct pollfd));
255 uint32_t *gens;
256
257 if (!pfds)
258 return -1;
259 g_pfds = pfds;
260 if (!(gens = realloc(g_generation, (size_t)space * sizeof(uint32_t))))
261 return -1;
262 g_generation = gens;
263 g_space = space;
264 }
265 return 0;
266}
267
268int event_add(int fd, int what, uint32_t generation) {
269 if (fd < 0 || event_room_for(fd) == -1)
270 return -1;
271 if (g_where[fd] != -1) {
272 errno = EEXIST;
273 return -1;
274 }
275
276 g_pfds[g_count].fd = fd;
277 g_pfds[g_count].events = event_to_poll(what);
278 g_pfds[g_count].revents = 0;
279 g_generation[g_count] = generation;
280 g_where[fd] = g_count++;
281 return 0;
282}
283
284int event_modify(int fd, int what, uint32_t generation) {
285 if (fd < 0 || fd >= g_where_space || g_where[fd] == -1) {
286 errno = ENOENT;
287 return -1;
288 }
289 g_pfds[g_where[fd]].events = event_to_poll(what);
290 g_generation[g_where[fd]] = generation;
291 return 0;
292}
293
294int event_del(int fd) {
295 int at;
296
297 if (fd < 0 || fd >= g_where_space || (at = g_where[fd]) == -1) {
298 errno = ENOENT;
299 return -1;
300 }
301
302 /* The last one moves into the gap, so the array stays packed */
303 g_pfds[at] = g_pfds[--g_count];
304 g_generation[at] = g_generation[g_count];
305 g_where[g_pfds[at].fd] = at;
306 g_where[fd] = -1;
307 return 0;
308}
309
310int event_wait(ot_event *out, int max, int timeout_ms) {
311 int ready, found = 0, i;
312
313 while ((ready = poll(g_pfds, (nfds_t)g_count, timeout_ms)) == -1 && errno == EINTR)
314 ;
315 if (ready <= 0)
316 return ready;
317
318 for (i = 0; i < g_count && found < max; ++i) {
319 short revents = g_pfds[i].revents;
320
321 if (!revents)
322 continue;
323 out[found].fd = g_pfds[i].fd;
324 out[found].generation = g_generation[i];
325 out[found].what = (revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL) ? EVENT_READ : 0) |
326 (revents & (POLLOUT | POLLHUP | POLLERR | POLLNVAL) ? EVENT_WRITE : 0);
327 ++found;
328 }
329 return found;
330}
331
332#endif /* OT_EVENT_POLL */
diff --git a/ot_event.h b/ot_event.h
new file mode 100644
index 0000000..c1627ab
--- /dev/null
+++ b/ot_event.h
@@ -0,0 +1,39 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6#ifndef OT_EVENT_H__
7#define OT_EVENT_H__
8
9#include <stdint.h>
10
11/* Which descriptors have become readable or writable, asked of whichever
12 mechanism the kernel offers. The generation a descriptor was registered
13 with is handed back with every event, so a caller can tell an event for
14 the connection it is holding from one for a connection that has since been
15 closed and had its number reused. */
16
17enum { EVENT_READ = 1, EVENT_WRITE = 2 };
18
19typedef struct {
20 int fd;
21 uint32_t generation;
22 int what;
23} ot_event;
24
25int event_init(void);
26void event_deinit(void);
27
28int event_add(int fd, int what, uint32_t generation);
29int event_modify(int fd, int what, uint32_t generation);
30int event_del(int fd);
31
32/* Waits at most timeout_ms, or indefinitely when that is negative, and fills
33 out with up to max ready descriptors. Returns how many, or -1. */
34int event_wait(ot_event *out, int max, int timeout_ms);
35
36/* Which mechanism was compiled in, for the version string */
37const char *event_mechanism(void);
38
39#endif
diff --git a/tests/runner.c b/tests/runner.c
index 9e5c99f..c2350c3 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -26,6 +26,7 @@ static const struct module modules[] = {{"ot_urlencoded", test_urlencoded},
26 {"ot_mutex", test_mutex}, 26 {"ot_mutex", test_mutex},
27 {"ot_batch", test_batch}, 27 {"ot_batch", test_batch},
28 {"ot_fdstate", test_fdstate}, 28 {"ot_fdstate", test_fdstate},
29 {"ot_event", test_event},
29 {"ot_accesslist", test_accesslist}, 30 {"ot_accesslist", test_accesslist},
30 {NULL, NULL}}; 31 {NULL, NULL}};
31 32
diff --git a/tests/t_event.c b/tests/t_event.c
new file mode 100644
index 0000000..306f8cd
--- /dev/null
+++ b/tests/t_event.c
@@ -0,0 +1,222 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <string.h>
8#include <sys/socket.h>
9#include <sys/time.h>
10#include <unistd.h>
11
12/* Opentracker */
13#include "ot_event.h"
14#include "test.h"
15
16/* Against real socket pairs: only the kernel can say whether a descriptor is
17 ready, so there is nothing useful to mock here. */
18
19#define EVENTS 16
20
21static int sv[2];
22static ot_event ready[EVENTS];
23
24static void pair_open(void) {
25 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1)
26 sv[0] = sv[1] = -1;
27}
28
29static void pair_close(void) {
30 if (sv[0] != -1)
31 close(sv[0]);
32 if (sv[1] != -1)
33 close(sv[1]);
34 sv[0] = sv[1] = -1;
35}
36
37static int wait_for(int fd, int what, int timeout_ms, uint32_t *generation) {
38 int count = event_wait(ready, EVENTS, timeout_ms), i;
39
40 for (i = 0; i < count; ++i)
41 if (ready[i].fd == fd && (ready[i].what & what)) {
42 if (generation)
43 *generation = ready[i].generation;
44 return 1;
45 }
46 return 0;
47}
48
49static unsigned long elapsed_ms(struct timeval *since) {
50 struct timeval now;
51
52 gettimeofday(&now, NULL);
53 return (unsigned long)((now.tv_sec - since->tv_sec) * 1000 + (now.tv_usec - since->tv_usec) / 1000);
54}
55
56static void case_readable(void) {
57 uint32_t generation = 0;
58
59 test_case("a descriptor with something to read is reported, with its generation");
60 pair_open();
61 CHECK(sv[0] != -1);
62 CHECK_INT(event_add(sv[0], EVENT_READ, 4711), 0);
63 CHECK_INT(write(sv[1], "x", 1), 1);
64 CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, &generation), 1);
65 CHECK_INT(generation, 4711);
66
67 test_case("and keeps being reported until it is read");
68 CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, NULL), 1);
69 {
70 char scratch[4];
71 CHECK_INT(read(sv[0], scratch, sizeof(scratch)), 1);
72 }
73 CHECK_INT(wait_for(sv[0], EVENT_READ, 50, NULL), 0);
74
75 CHECK_INT(event_del(sv[0]), 0);
76 pair_close();
77}
78
79static void case_writable(void) {
80 test_case("a descriptor with room to write is reported");
81 pair_open();
82 CHECK(sv[0] != -1);
83 CHECK_INT(event_add(sv[0], EVENT_WRITE, 1), 0);
84 CHECK_INT(wait_for(sv[0], EVENT_WRITE, 1000, NULL), 1);
85
86 test_case("and is not, once we say we no longer care");
87 CHECK_INT(event_modify(sv[0], EVENT_READ, 1), 0);
88 CHECK_INT(wait_for(sv[0], EVENT_WRITE, 50, NULL), 0);
89
90 CHECK_INT(event_del(sv[0]), 0);
91 pair_close();
92}
93
94static void case_interest_is_separate(void) {
95 test_case("asking only about writing does not report reading");
96 pair_open();
97 CHECK(sv[0] != -1);
98 CHECK_INT(event_add(sv[0], EVENT_WRITE, 1), 0);
99 CHECK_INT(write(sv[1], "x", 1), 1);
100 CHECK_INT(wait_for(sv[0], EVENT_READ, 50, NULL), 0);
101
102 test_case("until we ask about it");
103 CHECK_INT(event_modify(sv[0], EVENT_READ | EVENT_WRITE, 1), 0);
104 CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, NULL), 1);
105
106 CHECK_INT(event_del(sv[0]), 0);
107 pair_close();
108}
109
110static void case_removed_is_silent(void) {
111 test_case("a removed descriptor is not reported again");
112 pair_open();
113 CHECK(sv[0] != -1);
114 CHECK_INT(event_add(sv[0], EVENT_READ, 1), 0);
115 CHECK_INT(write(sv[1], "x", 1), 1);
116 CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, NULL), 1);
117 CHECK_INT(event_del(sv[0]), 0);
118 CHECK_INT(wait_for(sv[0], EVENT_READ, 50, NULL), 0);
119 pair_close();
120}
121
122static void case_generations_do_not_mix(void) {
123 int other[2];
124 uint32_t generation = 0;
125
126 test_case("two descriptors carry their own generations");
127 pair_open();
128 CHECK(sv[0] != -1);
129 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, other), 0);
130 CHECK_INT(event_add(sv[0], EVENT_READ, 100), 0);
131 CHECK_INT(event_add(other[0], EVENT_READ, 200), 0);
132 CHECK_INT(write(sv[1], "x", 1), 1);
133 CHECK_INT(write(other[1], "y", 1), 1);
134
135 CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, &generation), 1);
136 CHECK_INT(generation, 100);
137 CHECK_INT(wait_for(other[0], EVENT_READ, 1000, &generation), 1);
138 CHECK_INT(generation, 200);
139
140 test_case("and re-registering one changes only its own");
141 CHECK_INT(event_modify(sv[0], EVENT_READ, 101), 0);
142 CHECK_INT(wait_for(sv[0], EVENT_READ, 1000, &generation), 1);
143 CHECK_INT(generation, 101);
144 CHECK_INT(wait_for(other[0], EVENT_READ, 1000, &generation), 1);
145 CHECK_INT(generation, 200);
146
147 event_del(sv[0]);
148 event_del(other[0]);
149 close(other[0]);
150 close(other[1]);
151 pair_close();
152}
153
154/* Removing one of several has to leave the rest addressable: a backend that
155 keeps its descriptors packed moves the last one into the gap, and has to
156 remember where it went. */
157static void case_delete_from_the_middle(void) {
158 int pair[3][2];
159 uint32_t generation = 0;
160 int i;
161
162 test_case("deleting one of three leaves the others reported correctly");
163 for (i = 0; i < 3; ++i) {
164 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, pair[i]), 0);
165 CHECK_INT(event_add(pair[i][0], EVENT_READ, (uint32_t)(10 + i)), 0);
166 }
167 CHECK_INT(event_del(pair[1][0]), 0);
168
169 for (i = 0; i < 3; i += 2) {
170 CHECK_INT(write(pair[i][1], "x", 1), 1);
171 CHECK_INT(wait_for(pair[i][0], EVENT_READ, 1000, &generation), 1);
172 CHECK_INT(generation, 10 + i);
173 }
174
175 test_case("and the survivors can still be changed and removed");
176 CHECK_INT(event_modify(pair[2][0], EVENT_READ, 99), 0);
177 CHECK_INT(wait_for(pair[2][0], EVENT_READ, 1000, &generation), 1);
178 CHECK_INT(generation, 99);
179 CHECK_INT(event_del(pair[0][0]), 0);
180 CHECK_INT(event_del(pair[2][0]), 0);
181 CHECK_INT(wait_for(pair[2][0], EVENT_READ, 50, NULL), 0);
182
183 for (i = 0; i < 3; ++i)
184 close(pair[i][0]), close(pair[i][1]);
185}
186
187static void case_timeout(void) {
188 struct timeval started;
189
190 test_case("waiting on nothing comes back empty when the time is up");
191 gettimeofday(&started, NULL);
192 CHECK_INT(event_wait(ready, EVENTS, 200), 0);
193 {
194 unsigned long took = elapsed_ms(&started);
195 /* Scheduling makes the upper bound generous. The point is that it waited
196 rather than returning at once, and that it came back at all */
197 CHECK(took >= 150);
198 CHECK(took < 5000);
199 }
200}
201
202static void case_unknown_descriptor(void) {
203 test_case("changing or removing one that was never added is refused");
204 CHECK_INT(event_modify(4096, EVENT_READ, 1), -1);
205 CHECK_INT(event_del(4096), -1);
206}
207
208void test_event(void) {
209 CHECK_INT(event_init(), 0);
210 CHECK(event_mechanism() != NULL);
211
212 case_readable();
213 case_writable();
214 case_interest_is_separate();
215 case_removed_is_silent();
216 case_generations_do_not_mix();
217 case_delete_from_the_middle();
218 case_timeout();
219 case_unknown_descriptor();
220
221 event_deinit();
222}
diff --git a/tests/test.h b/tests/test.h
index cebd08a..aa4eb00 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -89,5 +89,6 @@ void test_socket(void);
89void test_mutex(void); 89void test_mutex(void);
90void test_batch(void); 90void test_batch(void);
91void test_fdstate(void); 91void test_fdstate(void);
92void test_event(void);
92 93
93#endif 94#endif