diff options
| author | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-26 20:03:22 +0100 |
|---|---|---|
| committer | Dirk Engling <erdgeist@erdgeist.org> | 2026-10-26 20:03:22 +0100 |
| commit | 2d6b6d4da1d9732b2b02989a4374d356ff840d1b (patch) | |
| tree | 50ff9d2fba93934d1572706f10593aec0ea57e07 /opentracker.c | |
| parent | f2f5b0450d4b7269bafdfb1bd1788303f6d8ce7b (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.c | 95 |
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 | ||
| 136 | static void handle_read(const int64_t sock, struct ot_workstruct *ws) { | 135 | static 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 | |||
| 232 | static void *server_mainloop(void *args) { | 236 | static 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 | } |
