master
c 451 lines 12.5 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "../libnetdata.h"
4 #include "nd-poll.h"
5
6 #ifndef POLLRDHUP
7 #define POLLRDHUP 0
8 #endif
9
10 #if defined(OS_LINUX)
11 #include <sys/epoll.h>
12
13 struct fd_info {
14 uint32_t events;
15 uint32_t last_served;
16 const void *data;
17 };
18
19 DEFINE_JUDYL_TYPED(POINTERS, struct fd_info *);
20
21 #define MAX_EVENTS_PER_CALL 100
22
23 // Event poll context
24 struct nd_poll_t {
25 int epoll_fd;
26
27 struct epoll_event ev[MAX_EVENTS_PER_CALL];
28 size_t last_pos;
29 size_t used;
30
31 POINTERS_JudyLSet pointers; // Judy array to store user data
32
33 uint32_t nfds; // the number of sockets we have
34 uint32_t iteration_counter;
35 };
36
37 // Initialize the event poll context
38 nd_poll_t *nd_poll_create() {
39 nd_poll_t *ndpl = callocz(1, sizeof(nd_poll_t));
40
41 ndpl->epoll_fd = epoll_create1(0);
42 if (ndpl->epoll_fd < 0) {
43 freez(ndpl);
44 return NULL;
45 }
46
47 return ndpl;
48 }
49
50 static inline uint32_t nd_poll_events_to_epoll_events(nd_poll_event_t events) {
51 uint32_t pevents = EPOLLERR | EPOLLHUP;
52 if (events & ND_POLL_READ) pevents |= EPOLLIN;
53 if (events & ND_POLL_WRITE) pevents |= EPOLLOUT;
54 return pevents;
55 }
56
57 static inline nd_poll_event_t nd_poll_events_from_epoll_events(uint32_t events) {
58 nd_poll_event_t nd_poll_events = ND_POLL_NONE;
59
60 if (events & (EPOLLIN|EPOLLPRI|EPOLLRDNORM|EPOLLRDBAND))
61 nd_poll_events |= ND_POLL_READ;
62
63 if (events & (EPOLLOUT|EPOLLWRNORM|EPOLLWRBAND))
64 nd_poll_events |= ND_POLL_WRITE;
65
66 if (events & EPOLLERR)
67 nd_poll_events |= ND_POLL_ERROR;
68
69 if (events & (EPOLLHUP|EPOLLRDHUP))
70 nd_poll_events |= ND_POLL_HUP;
71
72 return nd_poll_events;
73 }
74
75 // Add a file descriptor to the event poll
76 bool nd_poll_add(nd_poll_t *ndpl, int fd, nd_poll_event_t events, const void *data) {
77 internal_fatal(!data, "nd_poll() does not support NULL data pointers");
78
79 struct fd_info *fdi = mallocz(sizeof(*fdi));
80 fdi->data = data;
81 fdi->last_served = 0;
82 fdi->events = nd_poll_events_to_epoll_events(events);
83
84 if(POINTERS_GET(&ndpl->pointers, fd) || !POINTERS_SET(&ndpl->pointers, fd, fdi)) {
85 freez(fdi);
86 return false;
87 }
88
89 struct epoll_event ev = {
90 .events = fdi->events,
91 .data.fd = fd,
92 };
93
94 bool rc = epoll_ctl(ndpl->epoll_fd, EPOLL_CTL_ADD, fd, &ev) == 0;
95 if(rc)
96 ndpl->nfds++;
97 else {
98 POINTERS_DEL(&ndpl->pointers, fd);
99 freez(fdi);
100 }
101
102 internal_fatal(!rc, "epoll_ctl() failed");
103
104 return rc;
105 }
106
107 // Remove a file descriptor from the event poll
108 bool nd_poll_del(nd_poll_t *ndpl, int fd) {
109 struct fd_info *fdi = POINTERS_GET(&ndpl->pointers, fd);
110 if(!fdi) return false;
111
112 POINTERS_DEL(&ndpl->pointers, fd);
113 freez(fdi);
114
115 ndpl->nfds--; // we can't check for success/failure here, because epoll() removes fds when they are closed
116 bool rc = epoll_ctl(ndpl->epoll_fd, EPOLL_CTL_DEL, fd, NULL) == 0;
117 internal_error(!rc, "epoll_ctl() failed (is the socket already closed)"); // this is ok if the socket is already closed
118 return rc;
119 }
120
121 // Update an existing file descriptor in the event poll
122 ALWAYS_INLINE_HOT_FLATTEN
123 bool nd_poll_upd(nd_poll_t *ndpl, int fd, nd_poll_event_t events) {
124 struct fd_info *fdi = POINTERS_GET(&ndpl->pointers, fd);
125 if(!fdi) return false;
126
127 fdi->events = nd_poll_events_to_epoll_events(events);
128
129 struct epoll_event ev = {
130 .events = fdi->events,
131 .data.fd = fd,
132 };
133 bool rc = epoll_ctl(ndpl->epoll_fd, EPOLL_CTL_MOD, fd, &ev) == 0;
134 internal_fatal(!rc, "epoll_ctl() failed"); // this may happen if fd is closed
135 return rc;
136 }
137
138 static inline bool nd_poll_get_next_event(nd_poll_t *ndpl, nd_poll_result_t *result) {
139 while(ndpl->last_pos < ndpl->used) {
140 struct fd_info *fdi = POINTERS_GET(&ndpl->pointers, ndpl->ev[ndpl->last_pos].data.fd);
141
142 // Skip events that have been invalidated by nd_poll_del()
143 if(!fdi || !fdi->data) {
144 ndpl->last_pos++;
145 continue;
146 }
147
148 *result = (nd_poll_result_t){
149 .events = nd_poll_events_from_epoll_events(ndpl->ev[ndpl->last_pos].events & fdi->events),
150 .data = fdi->data,
151 };
152
153 ndpl->last_pos++;
154
155 if(!result->events)
156 // nd_poll_upd() may have removed some flags since we got this
157 continue;
158
159 fdi->last_served = ndpl->iteration_counter;
160 return true;
161 }
162
163 return false;
164 }
165
166 typedef struct {
167 struct epoll_event event;
168 uint32_t last_served;
169 } sortable_event_t;
170
171 static int compare_last_served(const void *a, const void *b) {
172 const sortable_event_t *ev_a = (const sortable_event_t *)a;
173 const sortable_event_t *ev_b = (const sortable_event_t *)b;
174
175 if (ev_a->last_served < ev_b->last_served)
176 return -1;
177 if (ev_a->last_served > ev_b->last_served)
178 return 1;
179
180 return 0;
181 }
182
183 static void sort_events(nd_poll_t *ndpl) {
184 if(ndpl->used <= 1) return;
185
186 internal_fatal(ndpl->used > MAX_EVENTS_PER_CALL, "ndpl->used exceeds MAX_EVENTS_PER_CALL");
187
188 sortable_event_t sortable_array[MAX_EVENTS_PER_CALL];
189 for (size_t i = 0; i < ndpl->used; ++i) {
190 struct fd_info *fdi = POINTERS_GET(&ndpl->pointers, ndpl->ev[i].data.fd);
191 sortable_array[i] = (sortable_event_t){
192 .event = ndpl->ev[i],
193 .last_served = fdi ? fdi->last_served : UINT32_MAX,
194 };
195 }
196
197 qsort(sortable_array, ndpl->used, sizeof(sortable_event_t), compare_last_served);
198
199 // Reorder `ndpl->ev` based on the sorted order
200 for (size_t i = 0; i < ndpl->used; ++i)
201 ndpl->ev[i] = sortable_array[i].event;
202 }
203
204 // Wait for events
205 ALWAYS_INLINE_HOT_FLATTEN
206 int nd_poll_wait(nd_poll_t *ndpl, int timeout_ms, nd_poll_result_t *result) {
207 ndpl->iteration_counter++;
208
209 if(nd_poll_get_next_event(ndpl, result))
210 return 1;
211
212 do {
213 errno_clear();
214 ndpl->last_pos = 0;
215 ndpl->used = 0;
216
217 int n = epoll_wait(ndpl->epoll_fd, &ndpl->ev[0], _countof(ndpl->ev), timeout_ms);
218
219 if(unlikely(n <= 0)) {
220 if(n == 0) {
221 result->events = ND_POLL_TIMEOUT;
222 result->data = NULL;
223 return 0;
224 }
225
226 if(errno == EINTR || errno == EAGAIN)
227 continue;
228
229 result->events = ND_POLL_POLL_FAILED;
230 result->data = NULL;
231 return -1;
232 }
233
234 ndpl->used = n;
235 ndpl->last_pos = 0;
236 sort_events(ndpl);
237 if (nd_poll_get_next_event(ndpl, result))
238 return 1;
239
240 internal_fatal(true, "nd_poll_get_next_event() should have 1 event!");
241 } while(true);
242 }
243
244 static void nd_poll_free_callback(Word_t fd __maybe_unused, struct fd_info *fdi, void *data __maybe_unused) {
245 freez(fdi);
246 }
247
248 // Destroy the event poll context
249 void nd_poll_destroy(nd_poll_t *ndpl) {
250 if (ndpl) {
251 close(ndpl->epoll_fd);
252 POINTERS_FREE(&ndpl->pointers, nd_poll_free_callback, NULL);
253 freez(ndpl);
254 }
255 }
256 #else
257
258 DEFINE_JUDYL_TYPED(POINTERS, const void *);
259
260 struct nd_poll_t {
261 struct pollfd *fds; // Array of file descriptors
262 nfds_t nfds; // Number of active file descriptors
263 nfds_t capacity; // Allocated capacity for `fds` array
264 nfds_t last_pos;
265 POINTERS_JudyLSet pointers; // Judy array to store user data
266 };
267
268 #define INITIAL_CAPACITY 4
269
270 // Initialize the event poll context
271 nd_poll_t *nd_poll_create() {
272 nd_poll_t *ndpl = callocz(1, sizeof(nd_poll_t));
273 ndpl->fds = mallocz(INITIAL_CAPACITY * sizeof(struct pollfd));
274 ndpl->nfds = 0;
275 ndpl->capacity = INITIAL_CAPACITY;
276
277 POINTERS_INIT(&ndpl->pointers);
278
279 return ndpl;
280 }
281
282 // Ensure capacity for adding new file descriptors
283 static void ensure_capacity(nd_poll_t *ndpl) {
284 if (ndpl->nfds < ndpl->capacity) return;
285
286 nfds_t new_capacity = ndpl->capacity * 2;
287 struct pollfd *new_fds = reallocz(ndpl->fds, new_capacity * sizeof(struct pollfd));
288
289 ndpl->fds = new_fds;
290 ndpl->capacity = new_capacity;
291 }
292
293 static inline short int nd_poll_events_to_poll_events(nd_poll_event_t events) {
294 short int pevents = POLLERR | POLLHUP | POLLNVAL;
295 if (events & ND_POLL_READ) pevents |= POLLIN;
296 if (events & ND_POLL_WRITE) pevents |= POLLOUT;
297 return pevents;
298 }
299
300 static inline nd_poll_event_t nd_poll_events_from_poll_revents(short int events) {
301 nd_poll_event_t nd_poll_events = ND_POLL_NONE;
302
303 if (events & (POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND))
304 nd_poll_events |= ND_POLL_READ;
305
306 if (events & (POLLOUT|POLLWRNORM|POLLWRBAND))
307 nd_poll_events |= ND_POLL_WRITE;
308
309 if (events & POLLERR)
310 nd_poll_events |= ND_POLL_ERROR;
311
312 if (events & (POLLHUP|POLLRDHUP))
313 nd_poll_events |= ND_POLL_HUP;
314
315 if (events & (POLLNVAL))
316 nd_poll_events |= ND_POLL_INVALID;
317
318 return nd_poll_events;
319 }
320
321 bool nd_poll_add(nd_poll_t *ndpl, int fd, nd_poll_event_t events, const void *data) {
322 internal_fatal(POINTERS_GET(&ndpl->pointers, fd) != NULL, "File descriptor %d is already served - cannot add", fd);
323
324 if(POINTERS_GET(&ndpl->pointers, fd) || !POINTERS_SET(&ndpl->pointers, fd, data))
325 return false;
326
327 ensure_capacity(ndpl);
328 struct pollfd *pfd = &ndpl->fds[ndpl->nfds++];
329 pfd->fd = fd;
330 pfd->events = nd_poll_events_to_poll_events(events);
331 pfd->revents = 0;
332
333 return true;
334 }
335
336 // Remove a file descriptor from the event poll
337 bool nd_poll_del(nd_poll_t *ndpl, int fd) {
338 if(!POINTERS_DEL(&ndpl->pointers, fd))
339 return false;
340
341 for (nfds_t i = 0; i < ndpl->nfds; i++) {
342 if (ndpl->fds[i].fd == fd) {
343
344 // Remove the file descriptor by shifting the array
345 memmove(&ndpl->fds[i], &ndpl->fds[i + 1], (ndpl->nfds - i - 1) * sizeof(struct pollfd));
346 ndpl->nfds--;
347
348 if(i < ndpl->last_pos)
349 ndpl->last_pos--;
350
351 return true;
352 }
353 }
354
355 return false; // File descriptor not found
356 }
357
358 // Update an existing file descriptor in the event poll
359 ALWAYS_INLINE_HOT_FLATTEN
360 bool nd_poll_upd(nd_poll_t *ndpl, int fd, nd_poll_event_t events) {
361 for (nfds_t i = 0; i < ndpl->nfds; i++) {
362 if (ndpl->fds[i].fd == fd) {
363 struct pollfd *pfd = &ndpl->fds[i];
364 pfd->events = nd_poll_events_to_poll_events(events);
365 return true;
366 }
367 }
368
369 // File descriptor not found
370 return false;
371 }
372
373 static inline bool nd_poll_get_next_event(nd_poll_t *ndpl, nd_poll_result_t *result) {
374 for (nfds_t i = ndpl->last_pos; i < ndpl->nfds; i++) {
375 if (ndpl->fds[i].revents != 0) {
376
377 result->data = POINTERS_GET(&ndpl->pointers, ndpl->fds[i].fd);
378 if(!result->data)
379 continue;
380
381 result->events = nd_poll_events_from_poll_revents(ndpl->fds[i].revents & ndpl->fds[i].events);
382 if(!result->events)
383 // nd_poll_upd() may have removed some flags since we got this
384 continue;
385
386 ndpl->fds[i].revents = 0;
387
388 ndpl->last_pos = i + 1;
389 return true;
390 }
391 }
392
393 ndpl->last_pos = ndpl->nfds;
394 return false;
395 }
396
397 // Rotate the fds array to prevent starvation
398 static inline void rotate_fds(nd_poll_t *ndpl) {
399 if (ndpl->nfds == 0 || ndpl->nfds == 1)
400 return; // No rotation needed for empty or single-entry arrays
401
402 struct pollfd first = ndpl->fds[0];
403 memmove(&ndpl->fds[0], &ndpl->fds[1], (ndpl->nfds - 1) * sizeof(struct pollfd));
404 ndpl->fds[ndpl->nfds - 1] = first;
405 }
406
407 // Wait for events
408 ALWAYS_INLINE_HOT_FLATTEN
409 int nd_poll_wait(nd_poll_t *ndpl, int timeout_ms, nd_poll_result_t *result) {
410 if (nd_poll_get_next_event(ndpl, result))
411 return 1; // Return immediately if there's a pending event
412
413 do {
414 errno_clear();
415 ndpl->last_pos = 0;
416 rotate_fds(ndpl); // Rotate the array on every wait
417 int ret = poll(ndpl->fds, ndpl->nfds, timeout_ms);
418
419 if(unlikely(ret <= 0)) {
420 if(ret == 0) {
421 result->events = ND_POLL_TIMEOUT;
422 result->data = NULL;
423 return 0;
424 }
425
426 if(errno == EAGAIN || errno == EINTR)
427 continue;
428
429 result->events = ND_POLL_POLL_FAILED;
430 result->data = NULL;
431 return -1;
432 }
433
434 // Process the next event
435 if (nd_poll_get_next_event(ndpl, result))
436 return 1;
437
438 internal_fatal(true, "nd_poll_get_next_event() should have 1 event!");
439 } while (true);
440 }
441
442 // Destroy the event poll context
443 void nd_poll_destroy(nd_poll_t *ndpl) {
444 if (ndpl) {
445 free(ndpl->fds);
446 POINTERS_FREE(&ndpl->pointers, NULL, NULL);
447 freez(ndpl);
448 }
449 }
450
451 #endif