summaryrefslogtreecommitdiff
path: root/opentracker.c
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-10-26 20:03:22 +0100
committerDirk Engling <erdgeist@erdgeist.org>2026-10-26 20:03:22 +0100
commit2d6b6d4da1d9732b2b02989a4374d356ff840d1b (patch)
tree50ff9d2fba93934d1572706f10593aec0ea57e07 /opentracker.c
parentf2f5b0450d4b7269bafdfb1bd1788303f6d8ce7b (diff)
Replace libowfat's event waiter with our own abstraction
It's with a heavy heart that I remove libowfat as my event library from opentracker. libowfat is what taught opentracker to walk and then fly. The tracker was literally standing on the shoulder of giants when I started writing it in 2006. For its time, the event abstraction was unparalleled and it has held up for many years, and it served the bittorrent community greatly. https://erdgeist.org/gitweb/opentracker/commit/?id=ad472597c569568bad0526d116b601dfcb36638c Unfortunately, it is very likely that libowfat will not be maintained anymore for the foreseeable future, it is already not easy to get its source from upstream and opentracker started receiving bug reports that I can not reasonably answer or patch. Before the project gets more dependent on the limitations of libowfat's current state, I need to teach opentracker to stand on its own feet and fly by itself. I've learned a great deal in the process and have even more respect now for what libowfat pioneered. The path for opentracker is cleared now. Ground work has already been laid in the migration process to support multi threaded http processing while keeping the aggressive reduction of syscalls alive that made the libowfat+opentracker combo so scalable. Here be some dragons, though: Even though there is a unit test suite now, the event loop's rewrite has not been battle tested yet. So you might want to hold up for a bit before jumping on the solo band wagon. What awaits at the end will be a long overdue v1.0, which will be named in honour of Felix.
Diffstat (limited to 'opentracker.c')
-rw-r--r--opentracker.c95
1 files changed, 57 insertions, 38 deletions
diff --git a/opentracker.c b/opentracker.c
index 7de6c6a..21eeb42 100644
--- a/opentracker.c
+++ b/opentracker.c
@@ -21,9 +21,6 @@
21#include <syslog.h> 21#include <syslog.h>
22#endif 22#endif
23 23
24/* Libowfat */
25#include "io.h"
26
27/* Opentracker */ 24/* Opentracker */
28#include "ot_accesslist.h" 25#include "ot_accesslist.h"
29#include "ot_config.h" 26#include "ot_config.h"
@@ -34,6 +31,7 @@
34#include "ot_mutex.h" 31#include "ot_mutex.h"
35#include "ot_random.h" 32#include "ot_random.h"
36#include "ot_scan.h" 33#include "ot_scan.h"
34#include "ot_event.h"
37#include "ot_fdstate.h" 35#include "ot_fdstate.h"
38#include "ot_socket.h" 36#include "ot_socket.h"
39#include "ot_stats.h" 37#include "ot_stats.h"
@@ -130,14 +128,15 @@ static void handle_dead(const int64_t sock) {
130 } 128 }
131 /* The generation has to move before the kernel can reissue the number */ 129 /* The generation has to move before the kernel can reissue the number */
132 fdstate_close(sock); 130 fdstate_close(sock);
133 io_close(sock); 131 event_del((int)sock);
132 close((int)sock);
134} 133}
135 134
136static void handle_read(const int64_t sock, struct ot_workstruct *ws) { 135static void handle_read(const int64_t sock, struct ot_workstruct *ws) {
137 struct http_data *cookie = fdstate_cookie(sock); 136 struct http_data *cookie = fdstate_cookie(sock);
138 ssize_t byte_count = io_tryread(sock, ws->inbuf, G_INBUF_SIZE); 137 ssize_t byte_count = read((int)sock, ws->inbuf, G_INBUF_SIZE);
139 138
140 if (byte_count == 0 || byte_count == -3) { 139 if (byte_count == 0 || (byte_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) {
141 handle_dead(sock); 140 handle_dead(sock);
142 return; 141 return;
143 } 142 }
@@ -197,7 +196,7 @@ static void handle_write(const int64_t sock) {
197 196
198 /* In a chunked transfer, wait for the next piece once everything queued has gone out */ 197 /* In a chunked transfer, wait for the next piece once everything queued has gone out */
199 if (chunked) 198 if (chunked)
200 io_dontwantwrite(sock); 199 http_interest_drop(sock, cookie, EVENT_OUT);
201 else 200 else
202 handle_dead(sock); 201 handle_dead(sock);
203} 202}
@@ -210,25 +209,30 @@ static void handle_accept(const int64_t serversocket) {
210 209
211 while ((sock = socket_accept(serversocket, ip, &port)) != -1) { 210 while ((sock = socket_accept(serversocket, ip, &port)) != -1) {
212 211
213 /* Put fd into a non-blocking mode */ 212 socket_set_blocking((int)sock, 0);
214 io_nonblock(sock);
215 213
216 if (!io_fd(sock) || !(cookie = (struct http_data *)malloc(sizeof(struct http_data)))) { 214 if (!(cookie = (struct http_data *)malloc(sizeof(struct http_data)))) {
217 io_close(sock); 215 close((int)sock);
218 continue; 216 continue;
219 } 217 }
220 memset(cookie, 0, sizeof(struct http_data)); 218 memset(cookie, 0, sizeof(struct http_data));
221 memcpy(cookie->ip, ip, sizeof(ot_ip6)); 219 memcpy(cookie->ip, ip, sizeof(ot_ip6));
220 cookie->interest = EVENT_IN;
222 221
223 fdstate_open(sock, cookie, g_now_seconds + OT_CLIENT_TIMEOUT); 222 if (event_add((int)sock, EVENT_IN, fdstate_open(sock, cookie, g_now_seconds + OT_CLIENT_TIMEOUT)) == -1) {
224 io_wantread(sock); 223 fdstate_close(sock);
224 free(cookie);
225 close((int)sock);
226 continue;
227 }
225 228
226 stats_issue_event(EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip); 229 stats_issue_event(EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip);
227
228 } 230 }
229 io_eagain(serversocket);
230} 231}
231 232
233/* How many ready descriptors one wait hands back at a time */
234#define OT_EVENT_BATCH 512
235
232static void *server_mainloop(void *args) { 236static void *server_mainloop(void *args) {
233 struct ot_workstruct ws; 237 struct ot_workstruct ws;
234 time_t next_timeout_check = g_now_seconds + OT_CLIENT_TIMEOUT_CHECKINTERVAL; 238 time_t next_timeout_check = g_now_seconds + OT_CLIENT_TIMEOUT_CHECKINTERVAL;
@@ -251,28 +255,42 @@ static void *server_mainloop(void *args) {
251 random_bytes(ws.rand48_state, sizeof(ws.rand48_state)); 255 random_bytes(ws.rand48_state, sizeof(ws.rand48_state));
252 256
253 for (;;) { 257 for (;;) {
254 int64_t sock; 258 ot_event ready[OT_EVENT_BATCH];
259 int64_t sock;
260 int count, i;
255 261
256 io_wait(); 262 count = event_wait(ready, OT_EVENT_BATCH, OT_CLIENT_TIMEOUT_CHECKINTERVAL * 1000);
263
264 for (i = 0; i < count; ++i) {
265 const void *cookie;
266
267 sock = ready[i].fd;
268 /* A descriptor closed earlier in this same batch may already have been
269 handed out again by accept, so an event is only ours if the
270 generation it was registered under is still the one in place */
271 if (fdstate_generation((int)sock) != ready[i].generation)
272 continue;
273 cookie = fdstate_cookie((int)sock);
257 274
258 while ((sock = io_canread()) != -1) {
259 const void *cookie = fdstate_cookie(sock);
260 if ((intptr_t)cookie == FLAG_TCP) 275 if ((intptr_t)cookie == FLAG_TCP)
261 handle_accept(sock); 276 handle_accept(sock);
262 else if ((intptr_t)cookie == FLAG_UDP) 277 else if ((intptr_t)cookie == FLAG_UDP)
263 handle_udp6(sock, &ws); 278 handle_udp6(sock, &ws);
264 else if ((intptr_t)cookie == FLAG_SELFPIPE) 279 else if ((intptr_t)cookie == FLAG_SELFPIPE)
265 io_tryread(sock, ws.inbuf, G_INBUF_SIZE); 280 while (read((int)sock, ws.inbuf, G_INBUF_SIZE) > 0)
266 else 281 ;
267 handle_read(sock, &ws); 282 else {
283 if (ready[i].what & EVENT_IN)
284 handle_read(sock, &ws);
285 /* The read may have closed it, so look again before writing */
286 if ((ready[i].what & EVENT_OUT) && fdstate_generation((int)sock) == ready[i].generation)
287 handle_write(sock);
288 }
268 } 289 }
269 290
270 while ((sock = mutex_workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1) 291 while ((sock = mutex_workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1)
271 http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial); 292 http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial);
272 293
273 while ((sock = io_canwrite()) != -1)
274 handle_write(sock);
275
276 if (g_now_seconds > next_timeout_check) { 294 if (g_now_seconds > next_timeout_check) {
277 int cursor = 0; 295 int cursor = 0;
278 296
@@ -319,11 +337,8 @@ static void listen_bind_one(ot_listen *entry) {
319 panic("socket_udp_bind"); 337 panic("socket_udp_bind");
320 } 338 }
321 339
322 if (!io_fd(entry->sock)) 340 if (event_add((int)entry->sock, EVENT_IN, fdstate_open(entry->sock, (void *)entry->proto, 0)) == -1)
323 panic("io_fd"); 341 panic("event_add");
324
325 fdstate_open(entry->sock, (void *)entry->proto, 0);
326 io_wantread(entry->sock);
327 342
328#ifdef _DEBUG 343#ifdef _DEBUG
329 fputs(" success.\n", stderr); 344 fputs(" success.\n", stderr);
@@ -352,7 +367,8 @@ static void listen_start_udp_workers(void) {
352 if (entry->proto != FLAG_UDP || entry->workers <= 0) 367 if (entry->proto != FLAG_UDP || entry->workers <= 0)
353 continue; 368 continue;
354 369
355 io_block(entry->sock); 370 socket_set_blocking((int)entry->sock, 1);
371 event_del((int)entry->sock);
356 udp_workers_install(entry->sock, (unsigned int)entry->workers); 372 udp_workers_install(entry->sock, (unsigned int)entry->workers);
357 } 373 }
358} 374}
@@ -453,6 +469,8 @@ int main(int argc, char **argv) {
453 random_init(); 469 random_init();
454 if (fdstate_init() == -1) 470 if (fdstate_init() == -1)
455 panic("fdstate_init"); 471 panic("fdstate_init");
472 if (event_init() == -1)
473 panic("event_init");
456 config_parse(argc, argv); 474 config_parse(argc, argv);
457 475
458 listen_bind_all(); 476 listen_bind_all();
@@ -471,15 +489,15 @@ int main(int argc, char **argv) {
471 pthread_create(&thread_id, NULL, time_caching_worker, NULL); 489 pthread_create(&thread_id, NULL, time_caching_worker, NULL);
472 490
473 /* Create our self pipe which allows us to interrupt mainloops 491 /* Create our self pipe which allows us to interrupt mainloops
474 io_wait in case some data is available to send out */ 492 wait in case some data is available to send out */
475 if (pipe(g_self_pipe) == -1) 493 if (pipe(g_self_pipe) == -1)
476 panic("selfpipe failed: "); 494 panic("selfpipe failed: ");
477 if (!io_fd(g_self_pipe[0])) 495 /* Both ends have to be non blocking: the loop drains the read end until it
478 panic("selfpipe io_fd failed: "); 496 is empty, and a worker nudging a full pipe has already made its point */
479 if (!io_fd(g_self_pipe[1])) 497 socket_set_blocking(g_self_pipe[0], 0);
480 panic("selfpipe io_fd failed: "); 498 socket_set_blocking(g_self_pipe[1], 0);
481 fdstate_open(g_self_pipe[0], (void *)FLAG_SELFPIPE, 0); 499 if (event_add(g_self_pipe[0], EVENT_IN, fdstate_open(g_self_pipe[0], (void *)FLAG_SELFPIPE, 0)) == -1)
482 io_wantread(g_self_pipe[0]); 500 panic("selfpipe event_add");
483 501
484 /* Init all sub systems. This call may fail with an exit() */ 502 /* Init all sub systems. This call may fail with an exit() */
485 trackerlogic_init(); 503 trackerlogic_init();
@@ -499,6 +517,7 @@ int main(int argc, char **argv) {
499 517
500 server_mainloop(0); 518 server_mainloop(0);
501 519
520 event_deinit();
502 fdstate_deinit(); 521 fdstate_deinit();
503 return 0; 522 return 0;
504} 523}